Skip to content
Closed
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
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,29 @@ auto: build27
build27:
virtualenv local --python=python2.7
local/bin/pip install --use-mirrors -r requirements.txt
local/bin/python setup.py develop

build26:
virtualenv local --python=python2.6
local/bin/pip install --use-mirrors -r requirements.txt
local/bin/pip install --use-mirrors -r requirements26.txt
local/bin/python setup.py develop

build33:
virtualenv local --python=python3.3
local/bin/pip install --use-mirrors -r requirements.txt
local/bin/python setup.py develop

build34:
virtualenv local --python=python3.4
local/bin/pip install --use-mirrors -r requirements.txt
local/bin/python setup.py develop


build35:
virtualenv local --python=python3.5
local/bin/pip install --use-mirrors -r requirements.txt
local/bin/python setup.py develop

test:
rm -f .coverage
Expand Down
2 changes: 1 addition & 1 deletion arrow/arrow.py
Original file line number Diff line number Diff line change
Expand Up @@ -1047,4 +1047,4 @@ def _get_timestamp_from_input(timestamp):
raise ValueError('cannot parse \'{0}\' as a timestamp'.format(timestamp))

Arrow.min = Arrow.fromdatetime(datetime.min)
Arrow.max = Arrow.fromdatetime(datetime.max)
Arrow.max = Arrow.fromdatetime(datetime.max)
53 changes: 31 additions & 22 deletions arrow/parser.py → arrow/parser.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
from __future__ import absolute_import
from __future__ import unicode_literals

from datetime import datetime
from cpython.datetime cimport datetime

from dateutil import tz
import re

Expand All @@ -20,7 +21,8 @@ class ParserError(RuntimeError):

class DateTimeParser(object):

_FORMAT_RE = re.compile('(YYY?Y?|MM?M?M?|Do|DD?D?D?|d?d?d?d|HH?|hh?|mm?|ss?|S+|ZZ?Z?|a|A|X)')
_FORMAT_RE = re.compile(
'(YYY?Y?|MM?M?M?|Do|DD?D?D?|d?d?d?d|HH?|hh?|mm?|ss?|S+|ZZ?Z?|a|A|X)')
_ESCAPE_RE = re.compile('\[[^\[\]]*\]')

_ONE_OR_MORE_DIGIT_RE = re.compile('\d+')
Expand All @@ -30,7 +32,6 @@ class DateTimeParser(object):
_TZ_RE = re.compile('[+\-]?\d{2}:?(\d{2})?')
_TZ_NAME_RE = re.compile('\w[\w+\-/]+')


_BASE_INPUT_RE_MAP = {
'YYYY': _FOUR_DIGIT_RE,
'YY': _TWO_DIGIT_RE,
Expand All @@ -57,11 +58,11 @@ class DateTimeParser(object):
SEPARATORS = ['-', '/', '.']

def __init__(self, locale='en_us', cache_size=0):

self.locale = locales.get_locale(locale)
self._input_re_map = self._BASE_INPUT_RE_MAP.copy()
self._input_re_map.update({
'MMMM': self._choice_re(self.locale.month_names[1:], re.IGNORECASE),
'MMMM': self._choice_re(self.locale.month_names[1:],
re.IGNORECASE),
'MMM': self._choice_re(self.locale.month_abbreviations[1:],
re.IGNORECASE),
'Do': re.compile(self.locale.ordinal_day_re),
Expand Down Expand Up @@ -105,9 +106,9 @@ def parse_iso(self, string):
has_tz = False
# generate required formats: YYYY-MM-DD, YYYY-MM-DD, YYYY
# using various separators: -, /, .
l = len(self.MARKERS)
formats = [separator.join(self.MARKERS[:l-i])
for i in range(l)
length = len(self.MARKERS)
formats = [separator.join(self.MARKERS[:length - i])
for i in range(length)
for separator in self.SEPARATORS]

if has_time and has_tz:
Expand All @@ -126,7 +127,6 @@ def _generate_pattern_re(self, fmt):
# 'YYYY-MM-DD' -> '(?P<YYYY>\d{4})-(?P<MM>\d{2})-(?P<DD>\d{2})'
tokens = []
offset = 0

# Extract the bracketed expressions to be reinserted later.
escaped_fmt = re.sub(self._ESCAPE_RE, "#", fmt)
# Any number of S is the same as one.
Expand All @@ -145,9 +145,11 @@ def _generate_pattern_re(self, fmt):
tokens.append(token)
# a pattern doesn't have the same length as the token
# it replaces! We keep the difference in the offset variable.
# This works because the string is scanned left-to-right and matches
# are returned in the order found by finditer.
fmt_pattern = fmt_pattern[:m.start() + offset] + input_pattern + fmt_pattern[m.end() + offset:]
# This works because the string is scanned left-to-right and
# matches are returned in the order found by finditer.
fmt_pattern = fmt_pattern[
:m.start() + offset] + input_pattern + fmt_pattern[m.end() +
offset:]
offset += len(input_pattern) - (m.end() - m.start())

final_fmt_pattern = ""
Expand All @@ -163,12 +165,10 @@ def _generate_pattern_re(self, fmt):
return tokens, re.compile(final_fmt_pattern, flags=re.IGNORECASE)

def parse(self, string, fmt):

if isinstance(fmt, list):
return self._parse_multiformat(string, fmt)

fmt_tokens, fmt_pattern_re = self._generate_pattern_re(fmt)

match = fmt_pattern_re.search(string)
if match is None:
raise ParserError('Failed to match \'{0}\' when parsing \'{1}\''
Expand Down Expand Up @@ -212,7 +212,8 @@ def _parse_token(self, token, value, parts):
parts['second'] = int(value)

elif token == 'S':
# We have the *most significant* digits of an arbitrary-precision integer.
# We have the *most significant* digits of an arbitrary-precision
# integer.
# We want the six most significant digits as an integer, rounded.
# FIXME: add nanosecond support somehow?
value = value.ljust(7, str('0'))
Expand Down Expand Up @@ -263,10 +264,14 @@ def _build_datetime(parts):
elif am_pm == 'am' and hour == 12:
hour = 0

return datetime(year=parts.get('year', 1), month=parts.get('month', 1),
day=parts.get('day', 1), hour=hour, minute=parts.get('minute', 0),
second=parts.get('second', 0), microsecond=parts.get('microsecond', 0),
tzinfo=parts.get('tzinfo'))
return datetime(year=parts.get('year', 1),
month=parts.get('month', 1),
day=parts.get('day', 1),
hour=hour,
minute=parts.get('minute', 0),
second=parts.get('second', 0),
microsecond=parts.get('microsecond', 0),
tzinfo=parts.get('tzinfo'))

def _parse_multiformat(self, string, formats):

Expand All @@ -280,7 +285,9 @@ def _parse_multiformat(self, string, formats):
pass

if _datetime is None:
raise ParserError('Could not match input to any of {0} on \'{1}\''.format(formats, string))
raise ParserError(
'Could not match input to any of {0} on \'{1}\''.format(
formats, string))

return _datetime

Expand All @@ -290,7 +297,8 @@ def _map_lookup(input_map, key):
try:
return input_map[key]
except KeyError:
raise ParserError('Could not match "{0}" to {1}'.format(key, input_map))
raise ParserError('Could not match "{0}" to {1}'.format(key,
input_map))

@staticmethod
def _try_timestamp(string):
Expand Down Expand Up @@ -339,6 +347,7 @@ def parse(cls, string):
tzinfo = tz.gettz(string)

if tzinfo is None:
raise ParserError('Could not parse timezone expression "{0}"'.format(string))
raise ParserError(
'Could not parse timezone expression "{0}"'.format(string))

return tzinfo
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ chai==1.1.1
sphinx==1.3.5
simplejson==3.6.5
backports.functools_lru_cache==1.2.1
Cython==0.25.2
mock==2.0.0
40 changes: 39 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,44 @@
import codecs
import os.path
import re
import os
import sys

try:
from setuptools import setup
except ImportError:
from distutils.core import setup

from Cython.Build import cythonize
from setuptools.command.build_ext import build_ext
from distutils.extension import Extension


# them to extension names in dotted notation
def scandir(dir, files=[]):
for file in os.listdir(dir):
path = os.path.join(dir, file)
if os.path.isfile(path) and path.endswith(".pyx"):
files.append(path.replace(os.path.sep, ".")[:-4])
elif os.path.isdir(path):
scandir(path, files)
return files


# generate an Extension object from its dotted name
def makeExtension(extName):
try:
version = sys.version_info.major
except:
version = 2
extPath = extName.replace(".", os.path.sep) + ".pyx"
return Extension(
extName,
[extPath],
include_dirs=["."], # adding the '.' to include_dirs is CRUCIAL!!
extra_compile_arg=["-O" + str(version), "-Wall"],
extra_link_args=['-g'],
)


def fpath(name):
Expand All @@ -24,6 +56,11 @@ def grep(attrname):


file_text = read(fpath('arrow/__init__.py'))
# get the list of extensions
extNames = scandir("arrow")

# and build up the set of Extension objects
extensions = cythonize([makeExtension(name) for name in extNames])

setup(
name='arrow',
Expand All @@ -34,6 +71,8 @@ def grep(attrname):
author='Chris Smith',
author_email="[email protected]",
license='Apache 2.0',
ext_modules=extensions,
cmdclass={'build_ext': build_ext},
packages=['arrow'],
zip_safe=False,
install_requires=[
Expand All @@ -52,4 +91,3 @@ def grep(attrname):
'Topic :: Software Development :: Libraries :: Python Modules'
]
)

Loading