Skip to content

Commit

Permalink
Drop support for Python 2.6
Browse files Browse the repository at this point in the history
I would humbly like to suggest dateutil drop support for Python 2.6.

The last release of Python 2.6 was 2013-10-29, over 3 years ago. It is
no longer receiving security fixes.

https://www.python.org/dev/peps/pep-0361/

The pip project itself has recently dropped support for 2.6. Their
numbers estimate that Python 2.6 accounts for ~2% of their downloads.

pypa/pip#4343

For projects that still use Python 2.6, they can continue to pip install
an older version.

I've tried my best to remove as much 2.6 specific code as I can,
including the 'Programming Language :: Python :: 2.6' trove classifier
from setup.py. I've also removed Travis CI testing, which should result
in faster testing and fewer wasted resources.

Code changed:

- Removed Python2.6 from testing configuration
- setup.py cleanups due to fewer version complications
- Removed unittest2 dependency and monkey patching
- Use set literals
- Use dict comprehension
- Remove total_seconds workaround
- Remove TarFile.open() context manager workaround

Thanks for considering.
  • Loading branch information
jdufresne authored and justanr committed Jul 29, 2017
1 parent 27c352b commit 3a215f7
Show file tree
Hide file tree
Showing 18 changed files with 46 additions and 94 deletions.
2 changes: 0 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
language: python
cache: pip
python:
- "2.6"
- "2.7"
- "3.2"
- "3.3"
Expand All @@ -25,7 +24,6 @@ before_install:

install:
- pip install six
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi
- ./ci_tools/retry.sh python updatezinfo.py

script:
Expand Down
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
UNRELEASED
----------
- **Dropped support for Python 2.6.**

Version 2.6.1
-------------
- Updated zoneinfo file to 2017b. (gh pr #395)
Expand Down
2 changes: 1 addition & 1 deletion dateutil/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False,
# keep up with the last token skipped so we can recombine
# consecutively skipped tokens (-2 for when i begins at 0).
last_skipped_token_i = -2
skipped_tokens = list()
skipped_tokens = []

try:
# year/month/day list
Expand Down
8 changes: 4 additions & 4 deletions dateutil/rrule.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,13 +584,13 @@ def __init__(self, freq, dtstart=None,
self._byweekday = tuple(sorted(self._byweekday))
orig_byweekday = [weekday(x) for x in self._byweekday]
else:
orig_byweekday = tuple()
orig_byweekday = ()

if self._bynweekday is not None:
self._bynweekday = tuple(sorted(self._bynweekday))
orig_bynweekday = [weekday(*x) for x in self._bynweekday]
else:
orig_bynweekday = tuple()
orig_bynweekday = ()

if 'byweekday' not in self._original_rule:
self._original_rule['byweekday'] = tuple(itertools.chain(
Expand All @@ -599,7 +599,7 @@ def __init__(self, freq, dtstart=None,
# byhour
if byhour is None:
if freq < HOURLY:
self._byhour = set((dtstart.hour,))
self._byhour = {dtstart.hour}
else:
self._byhour = None
else:
Expand All @@ -619,7 +619,7 @@ def __init__(self, freq, dtstart=None,
# byminute
if byminute is None:
if freq < MINUTELY:
self._byminute = set((dtstart.minute,))
self._byminute = {dtstart.minute}
else:
self._byminute = None
else:
Expand Down
17 changes: 0 additions & 17 deletions dateutil/test/_common.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
from __future__ import unicode_literals
try:
import unittest2 as unittest
except ImportError:
import unittest

import os
import datetime
import time
Expand Down Expand Up @@ -209,18 +204,6 @@ def set_current_tz(self, tzname):
(err or 'Unknown error.'))


###
# Compatibility functions

def _total_seconds(td):
# Python 2.6 doesn't have a total_seconds() method on timedelta objects
return ((td.seconds + td.days * 86400) * 1000000 +
td.microseconds) // 1000000


total_seconds = getattr(datetime.timedelta, 'total_seconds', _total_seconds)


###
# Utility classes
class NotAValueClass(object):
Expand Down
6 changes: 1 addition & 5 deletions dateutil/test/test_easter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@
from dateutil.easter import EASTER_WESTERN, EASTER_ORTHODOX, EASTER_JULIAN

from datetime import date

try:
import unittest2 as unittest
except ImportError:
import unittest
import unittest

# List of easters between 1990 and 2050
western_easter_dates = [
Expand Down
6 changes: 1 addition & 5 deletions dateutil/test/test_imports.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import sys

try:
import unittest2 as unittest
except ImportError:
import unittest
import unittest

class ImportVersionTest(unittest.TestCase):
""" Test that dateutil.__version__ can be imported"""
Expand Down
2 changes: 1 addition & 1 deletion dateutil/test/test_parser.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from ._common import unittest

from datetime import datetime, timedelta
import unittest

from dateutil.tz import tzoffset
from dateutil.parser import parse, parserinfo
Expand Down
3 changes: 2 additions & 1 deletion dateutil/test/test_relativedelta.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from ._common import unittest, WarningTestMixin, NotAValue
from ._common import WarningTestMixin, NotAValue

import calendar
from datetime import datetime, date, timedelta
import unittest

from dateutil.relativedelta import relativedelta, MO, TU, WE, FR, SU

Expand Down
3 changes: 2 additions & 1 deletion dateutil/test/test_rrule.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from ._common import WarningTestMixin, unittest
from ._common import WarningTestMixin

from datetime import datetime, date
import unittest
from six import PY3

from dateutil.rrule import (
Expand Down
20 changes: 10 additions & 10 deletions dateutil/test/test_tz.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from ._common import unittest, PicklableMixin
from ._common import total_seconds
from ._common import PicklableMixin
from ._common import TZEnvContext, TZWinContext
from ._common import WarningTestMixin
from ._common import ComparesEqual
Expand All @@ -10,6 +9,7 @@
from datetime import time as dt_time
from datetime import tzinfo
from six import BytesIO, StringIO
import unittest

import sys
import base64
Expand Down Expand Up @@ -280,7 +280,7 @@ def testFoldLondon(self):

self.assertEqual(t0.replace(tzinfo=None),
datetime(2013, 10, 27, 1, 30))

self.assertEqual(t1.replace(tzinfo=None),
datetime(2013, 10, 27, 1, 30))

Expand Down Expand Up @@ -317,14 +317,14 @@ def testFoldIndependence(self):

def testInZoneFoldEquality(self):
# Two datetimes in the same zone are considered to be equal if their
# wall times are equal, even if they have different absolute times.
# wall times are equal, even if they have different absolute times.

tzname = self._get_tzname('America/New_York')

with self._gettz_context(tzname):
NYC = self.gettz(tzname)
UTC = tz.tzutc()

dt0 = datetime(2011, 11, 6, 1, 30, tzinfo=NYC)
dt1 = tz.enfold(dt0, fold=1)

Expand Down Expand Up @@ -574,7 +574,7 @@ def testFoldIndependence(self):

def testInZoneFoldEquality(self):
# Two datetimes in the same zone are considered to be equal if their
# wall times are equal, even if they have different absolute times.
# wall times are equal, even if they have different absolute times.
tzname = 'Eastern Standard Time'
args = self.get_args(tzname)

Expand All @@ -583,7 +583,7 @@ def testInZoneFoldEquality(self):
UTC = tz.tzutc()

t_n, t0_u, t1_u = self.get_utc_transitions(NYC, 2011, False)

dt0 = t_n.replace(tzinfo=NYC)
dt1 = tz.enfold(dt0, fold=1)

Expand Down Expand Up @@ -714,8 +714,8 @@ def testEquality(self):

def testInequalityFixedOffset(self):
tzl = tz.tzlocal()
tzos = tz.tzoffset('LST', total_seconds(tzl._std_offset))
tzod = tz.tzoffset('LDT', total_seconds(tzl._std_offset))
tzos = tz.tzoffset('LST', tzl._std_offset.total_seconds())
tzod = tz.tzoffset('LDT', tzl._std_offset.total_seconds())

self.assertFalse(tzl == tzos)
self.assertFalse(tzl == tzod)
Expand Down Expand Up @@ -1745,7 +1745,7 @@ def setUp(self):
self.context = TZWinContext

def get_args(self, tzname):
return tuple()
return ()

def testLocal(self):
# Not sure how to pin a local time zone, so for now we're just going
Expand Down
9 changes: 0 additions & 9 deletions dateutil/tz/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,12 +383,3 @@ def __repr__(self):
return "%s(...)" % self.__class__.__name__

__reduce__ = object.__reduce__


def _total_seconds(td):
# Python 2.6 doesn't have a total_seconds() method on timedelta objects
return ((td.seconds + td.days * 86400) * 1000000 +
td.microseconds) // 1000000


_total_seconds = getattr(timedelta, 'total_seconds', _total_seconds)
14 changes: 7 additions & 7 deletions dateutil/tz/tz.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import bisect

from six import string_types
from ._common import tzname_in_python2, _tzinfo, _total_seconds
from ._common import tzname_in_python2, _tzinfo
from ._common import tzrangebase, enfold
from ._common import _validate_fromutc_inputs

Expand Down Expand Up @@ -101,7 +101,7 @@ def __init__(self, name, offset):

try:
# Allow a timedelta
offset = _total_seconds(offset)
offset = offset.total_seconds()
except (TypeError, AttributeError):
pass
self._offset = datetime.timedelta(seconds=offset)
Expand Down Expand Up @@ -150,7 +150,7 @@ def __ne__(self, other):
def __repr__(self):
return "%s(%s, %s)" % (self.__class__.__name__,
repr(self._name),
int(_total_seconds(self._offset)))
int(self._offset.total_seconds()))

__reduce__ = object.__reduce__

Expand Down Expand Up @@ -616,7 +616,7 @@ def _find_last_transition(self, dt, in_utc=False):
idx = bisect.bisect_right(trans_list, timestamp)

# We want to know when the previous transition was, so subtract off 1
return idx - 1
return idx - 1

def _get_ttinfo(self, idx):
# For no list or after the last transition, default to _ttinfo_std
Expand Down Expand Up @@ -850,12 +850,12 @@ def __init__(self, stdabbr, stdoffset=None,
self._dst_abbr = dstabbr

try:
stdoffset = _total_seconds(stdoffset)
stdoffset = stdoffset.total_seconds()
except (TypeError, AttributeError):
pass

try:
dstoffset = _total_seconds(dstoffset)
dstoffset = dstoffset.total_seconds()
except (TypeError, AttributeError):
pass

Expand Down Expand Up @@ -1491,7 +1491,7 @@ def _datetime_to_timestamp(dt):
Convert a :class:`datetime.datetime` object to an epoch timestamp in seconds
since January 1, 1970, ignoring the time zone.
"""
return _total_seconds((dt.replace(tzinfo=None) - EPOCH))
return (dt.replace(tzinfo=None) - EPOCH).total_seconds()


class _ContextWrapper(object):
Expand Down
1 change: 0 additions & 1 deletion dateutil/tz/win.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,6 @@ class tzwin(tzwinbase):
def __init__(self, name):
self._name = name

# multiple contexts only possible in 2.7 and 3.1, we still support 2.6
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
tzkeyname = text_type("{kn}\\{name}").format(kn=TZKEYNAME, name=name)
with winreg.OpenKey(handle, tzkeyname) as tzkey:
Expand Down
33 changes: 9 additions & 24 deletions dateutil/zoneinfo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,6 @@
ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
METADATA_FN = 'METADATA'

# python2.6 compatability. Note that TarFile.__exit__ != TarFile.close, but
# it's close enough for python2.6
tar_open = TarFile.open
if not hasattr(TarFile, '__exit__'):
def tar_open(*args, **kwargs):
return closing(TarFile.open(*args, **kwargs))


class tzfile(tzfile):
def __reduce__(self):
Expand All @@ -38,23 +31,15 @@ def getzoneinfofile_stream():
class ZoneInfoFile(object):
def __init__(self, zonefile_stream=None):
if zonefile_stream is not None:
with tar_open(fileobj=zonefile_stream, mode='r') as tf:
# dict comprehension does not work on python2.6
# TODO: get back to the nicer syntax when we ditch python2.6
# self.zones = {zf.name: tzfile(tf.extractfile(zf),
# filename = zf.name)
# for zf in tf.getmembers() if zf.isfile()}
self.zones = dict((zf.name, tzfile(tf.extractfile(zf),
filename=zf.name))
for zf in tf.getmembers()
if zf.isfile() and zf.name != METADATA_FN)
with TarFile.open(fileobj=zonefile_stream) as tf:
self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name)
for zf in tf.getmembers()
if zf.isfile() and zf.name != METADATA_FN}
# deal with links: They'll point to their parent object. Less
# waste of memory
# links = {zl.name: self.zones[zl.linkname]
# for zl in tf.getmembers() if zl.islnk() or zl.issym()}
links = dict((zl.name, self.zones[zl.linkname])
for zl in tf.getmembers() if
zl.islnk() or zl.issym())
links = {zl.name: self.zones[zl.linkname]
for zl in tf.getmembers() if
zl.islnk() or zl.issym()}
self.zones.update(links)
try:
metadata_json = tf.extractfile(tf.getmember(METADATA_FN))
Expand All @@ -64,7 +49,7 @@ def __init__(self, zonefile_stream=None):
# no metadata in tar file
self.metadata = None
else:
self.zones = dict()
self.zones = {}
self.metadata = None

def get(self, name, default=None):
Expand All @@ -90,7 +75,7 @@ def get(self, name, default=None):
# timezone. Ugly, but adheres to the api.
#
# TODO: Remove after deprecation period.
_CLASS_ZONE_INSTANCE = list()
_CLASS_ZONE_INSTANCE = []


def get_zonefile_instance(new_instance=False):
Expand Down
Loading

0 comments on commit 3a215f7

Please sign in to comment.