Skip to content

Commit

Permalink
Support for parsing ordinal days of month and docs.
Browse files Browse the repository at this point in the history
  • Loading branch information
chrishaines committed Jun 2, 2015
1 parent 1d89292 commit 2953545
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 6 deletions.
4 changes: 4 additions & 0 deletions arrow/locales.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ class Locale(object):
day_names = []
day_abbreviations = []

ordinal_day_re = r'(\d+)'

def __init__(self):

self._month_name_to_ordinal = None
Expand Down Expand Up @@ -205,6 +207,8 @@ class EnglishLocale(Locale):
day_names = ['', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
day_abbreviations = ['', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']

ordinal_day_re = r'((?P<value>[2-3]?1(?=st)|[2-3]?2(?=nd)|[2-3]?3(?=rd)|[1-3]?[04-9](?=th)|1[1-3](?=th))(st|nd|rd|th))'

def _ordinal_number(self, n):
if n % 100 not in (11, 12, 13):
remainder = abs(n) % 10
Expand Down
16 changes: 10 additions & 6 deletions arrow/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ParserError(RuntimeError):

class DateTimeParser(object):

_FORMAT_RE = re.compile('(YYY?Y?|MM?M?M?|DD?D?D?|HH?|hh?|mm?|ss?|SS?S?S?S?S?|ZZ?|a|A|X)')
_FORMAT_RE = re.compile('(YYY?Y?|MM?M?M?|Do|DD?D?D?|HH?|hh?|mm?|ss?|SS?S?S?S?S?|ZZ?|a|A|X)')

_ONE_THROUGH_SIX_DIGIT_RE = re.compile('\d{1,6}')
_ONE_THROUGH_FIVE_DIGIT_RE = re.compile('\d{1,5}')
Expand Down Expand Up @@ -63,7 +63,8 @@ def __init__(self, locale='en_us'):
self._input_re_map.update({
'MMMM': self._choice_re(self.locale.month_names[1:], re.IGNORECASE),
'MMM': self._choice_re(self.locale.month_abbreviations[1:],
re.IGNORECASE)
re.IGNORECASE),
'Do': re.compile(self.locale.ordinal_day_re)
})

def parse_iso(self, string):
Expand Down Expand Up @@ -116,7 +117,6 @@ def parse(self, string, fmt):
parts = {}

for token in tokens:

try:
input_re = self._input_re_map[token]
except KeyError:
Expand All @@ -125,10 +125,11 @@ def parse(self, string, fmt):
match = input_re.search(string)

if match:

token_values.append(match.group(0))
self._parse_token(token, match.group(0), parts)

if 'value' in match.groupdict():
self._parse_token(token, match.groupdict()['value'], parts)
else:
self._parse_token(token, match.group(0), parts)
index = match.span(0)[1]
string = string[index:]

Expand Down Expand Up @@ -177,6 +178,9 @@ def _parse_token(self, token, value, parts):
elif token in ['DD', 'D']:
parts['day'] = int(value)

elif token in ['Do']:
parts['day'] = int(value)

elif token.upper() in ['HH', 'H']:
parts['hour'] = int(value)

Expand Down
2 changes: 2 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,8 @@ Use the following tokens in parsing and formatting:
+--------------------------------+--------------+-------------------------------------------+
| |D |1, 2, 3 ... 30, 31 |
+--------------------------------+--------------+-------------------------------------------+
| |Do |1st, 2nd, 3rd ... 30th, 31st |
+--------------------------------+--------------+-------------------------------------------+
|**Day of Week** |dddd |Monday, Tuesday, Wednesday ... |
+--------------------------------+--------------+-------------------------------------------+
| |ddd |Mon, Tue, Wed ... |
Expand Down
53 changes: 53 additions & 0 deletions tests/parser_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import time

from arrow import parser
from arrow.parser import ParserError


class DateTimeParserTests(Chai):
Expand Down Expand Up @@ -485,3 +486,55 @@ def test_localized_month_abbreviation(self):
parser_.parse('2013-Gen-01', 'YYYY-MMM-DD'),
datetime(2013, 1, 1)
)

class DateTimeParserMonthOrdinalDayTests(Chai):

def setUp(self):
super(DateTimeParserMonthOrdinalDayTests, self).setUp()

self.parser = parser.DateTimeParser('en_us')

def test_matches(self):
assertEqual(
self.parser.parse('January 1st, 2013', 'MMMM Do, YYYY'),
datetime(2013, 1, 1)
)
assertEqual(
self.parser.parse('January 2nd, 2013', 'MMMM Do, YYYY'),
datetime(2013, 1, 2)
)
assertEqual(
self.parser.parse('January 3rd, 2013', 'MMMM Do, YYYY'),
datetime(2013, 1, 3)
)
assertEqual(
self.parser.parse('January 4th, 2013', 'MMMM Do, YYYY'),
datetime(2013, 1, 4)
)
assertEqual(
self.parser.parse('January 11th, 2013', 'MMMM Do, YYYY'),
datetime(2013, 1, 11)
)
assertEqual(
self.parser.parse('January 12th, 2013', 'MMMM Do, YYYY'),
datetime(2013, 1, 12)
)
assertEqual(
self.parser.parse('January 13th, 2013', 'MMMM Do, YYYY'),
datetime(2013, 1, 13)
)
assertEqual(
self.parser.parse('January 21st, 2013', 'MMMM Do, YYYY'),
datetime(2013, 1, 21)
)
assertEqual(
self.parser.parse('January 31st, 2013', 'MMMM Do, YYYY'),
datetime(2013, 1, 31)
)

def test_no_match(self):
with assertRaises(ParserError):
self.parser.parse('January 1th, 2013', 'MMMM Do, YYYY')

with assertRaises(ParserError):
self.parser.parse('January 11st, 2013', 'MMMM Do, YYYY')

0 comments on commit 2953545

Please sign in to comment.