mirror of
https://github.com/beetbox/beets.git
synced 2025-12-28 11:32:30 +01:00
Merge pull request #2528 from discopatrick/query-datetime-parser
Query datetime parser
This commit is contained in:
commit
389aed8d5d
3 changed files with 121 additions and 11 deletions
|
|
@ -533,12 +533,20 @@ class Period(object):
|
|||
instants of time during January 2014.
|
||||
"""
|
||||
|
||||
precisions = ('year', 'month', 'day')
|
||||
date_formats = ('%Y', '%Y-%m', '%Y-%m-%d')
|
||||
precisions = ('year', 'month', 'day', 'hour', 'minute', 'second')
|
||||
date_formats = (
|
||||
('%Y',), # year
|
||||
('%Y-%m',), # month
|
||||
('%Y-%m-%d',), # day
|
||||
('%Y-%m-%dT%H', '%Y-%m-%d %H'), # hour
|
||||
('%Y-%m-%dT%H:%M', '%Y-%m-%d %H:%M'), # minute
|
||||
('%Y-%m-%dT%H:%M:%S', '%Y-%m-%d %H:%M:%S') # second
|
||||
)
|
||||
|
||||
def __init__(self, date, precision):
|
||||
"""Create a period with the given date (a `datetime` object) and
|
||||
precision (a string, one of "year", "month", or "day").
|
||||
precision (a string, one of "year", "month", "day", "hour", "minute",
|
||||
or "second").
|
||||
"""
|
||||
if precision not in Period.precisions:
|
||||
raise ValueError(u'Invalid precision {0}'.format(precision))
|
||||
|
|
@ -551,16 +559,21 @@ class Period(object):
|
|||
string is empty, or raise an InvalidQueryArgumentValueError if
|
||||
the string could not be parsed to a date.
|
||||
"""
|
||||
|
||||
def find_date_and_format(string):
|
||||
for ord, format in enumerate(cls.date_formats):
|
||||
for format_option in format:
|
||||
try:
|
||||
date = datetime.strptime(string, format_option)
|
||||
return date, ord
|
||||
except ValueError:
|
||||
# Parsing failed.
|
||||
pass
|
||||
return (None, None)
|
||||
|
||||
if not string:
|
||||
return None
|
||||
date = None
|
||||
for ordinal, date_format in enumerate(cls.date_formats):
|
||||
try:
|
||||
date = datetime.strptime(string, date_format)
|
||||
break
|
||||
except ValueError:
|
||||
# Parsing failed.
|
||||
pass
|
||||
date, ordinal = find_date_and_format(string)
|
||||
if date is None:
|
||||
raise InvalidQueryArgumentValueError(string,
|
||||
'a valid datetime string')
|
||||
|
|
@ -582,6 +595,12 @@ class Period(object):
|
|||
return date.replace(year=date.year + 1, month=1)
|
||||
elif 'day' == precision:
|
||||
return date + timedelta(days=1)
|
||||
elif 'hour' == precision:
|
||||
return date + timedelta(hours=1)
|
||||
elif 'minute' == precision:
|
||||
return date + timedelta(minutes=1)
|
||||
elif 'second' == precision:
|
||||
return date + timedelta(seconds=1)
|
||||
else:
|
||||
raise ValueError(u'unhandled precision {0}'.format(precision))
|
||||
|
||||
|
|
|
|||
|
|
@ -188,6 +188,33 @@ Find all items with a file modification time between 2008-12-01 and
|
|||
|
||||
$ beet ls 'mtime:2008-12-01..2008-12-02'
|
||||
|
||||
You can also add an optional time value to date queries, specifying hours,
|
||||
minutes, and seconds.
|
||||
|
||||
Times are separated from dates by a space, an uppercase 'T' or a lowercase
|
||||
't', for example: ``2008-12-01T23:59:59``. If you specify a time, then the
|
||||
date must contain a year, month, and day. The minutes and seconds are
|
||||
optional.
|
||||
|
||||
Here is an example that finds all items added on 2008-12-01 at or after 22:00
|
||||
but before 23:00::
|
||||
|
||||
$ beet ls 'added:2008-12-01T22'
|
||||
|
||||
Find all items added on or after 2008-12-01 22:45::
|
||||
|
||||
$ beet ls 'added:2008-12-01T22:45..'
|
||||
|
||||
Find all items added on 2008-12-01, at or after 22:45:20 but before 22:45:41::
|
||||
|
||||
$ beet ls 'added:2008-12-01T22:45:20..2008-12-01T22:45:40'
|
||||
|
||||
Examples of each time format::
|
||||
|
||||
$ beet ls 'added:2008-12-01T22:45:20'
|
||||
$ beet ls 'added:2008-12-01t22:45:20'
|
||||
$ beet ls 'added:2008-12-01 22:45:20'
|
||||
|
||||
.. _not_query:
|
||||
|
||||
Query Term Negation
|
||||
|
|
|
|||
|
|
@ -58,6 +58,51 @@ class DateIntervalTest(unittest.TestCase):
|
|||
self.assertExcludes('1999-12..2000-02', '1999-11-30T23:59:59')
|
||||
self.assertExcludes('1999-12..2000-02', '2000-03-01T00:00:00')
|
||||
|
||||
def test_hour_precision_intervals(self):
|
||||
# test with 'T' separator
|
||||
self.assertExcludes('2000-01-01T12..2000-01-01T13',
|
||||
'2000-01-01T11:59:59')
|
||||
self.assertContains('2000-01-01T12..2000-01-01T13',
|
||||
'2000-01-01T12:00:00')
|
||||
self.assertContains('2000-01-01T12..2000-01-01T13',
|
||||
'2000-01-01T12:30:00')
|
||||
self.assertContains('2000-01-01T12..2000-01-01T13',
|
||||
'2000-01-01T13:30:00')
|
||||
self.assertContains('2000-01-01T12..2000-01-01T13',
|
||||
'2000-01-01T13:59:59')
|
||||
self.assertExcludes('2000-01-01T12..2000-01-01T13',
|
||||
'2000-01-01T14:00:00')
|
||||
self.assertExcludes('2000-01-01T12..2000-01-01T13',
|
||||
'2000-01-01T14:30:00')
|
||||
|
||||
# test non-range query
|
||||
self.assertContains('2008-12-01T22',
|
||||
'2008-12-01T22:30:00')
|
||||
self.assertExcludes('2008-12-01T22',
|
||||
'2008-12-01T23:30:00')
|
||||
|
||||
def test_minute_precision_intervals(self):
|
||||
self.assertExcludes('2000-01-01T12:30..2000-01-01T12:31',
|
||||
'2000-01-01T12:29:59')
|
||||
self.assertContains('2000-01-01T12:30..2000-01-01T12:31',
|
||||
'2000-01-01T12:30:00')
|
||||
self.assertContains('2000-01-01T12:30..2000-01-01T12:31',
|
||||
'2000-01-01T12:30:30')
|
||||
self.assertContains('2000-01-01T12:30..2000-01-01T12:31',
|
||||
'2000-01-01T12:31:59')
|
||||
self.assertExcludes('2000-01-01T12:30..2000-01-01T12:31',
|
||||
'2000-01-01T12:32:00')
|
||||
|
||||
def test_second_precision_intervals(self):
|
||||
self.assertExcludes('2000-01-01T12:30:50..2000-01-01T12:30:55',
|
||||
'2000-01-01T12:30:49')
|
||||
self.assertContains('2000-01-01T12:30:50..2000-01-01T12:30:55',
|
||||
'2000-01-01T12:30:50')
|
||||
self.assertContains('2000-01-01T12:30:50..2000-01-01T12:30:55',
|
||||
'2000-01-01T12:30:55')
|
||||
self.assertExcludes('2000-01-01T12:30:50..2000-01-01T12:30:55',
|
||||
'2000-01-01T12:30:56')
|
||||
|
||||
def test_unbounded_endpoints(self):
|
||||
self.assertContains('..', date=datetime.max)
|
||||
self.assertContains('..', date=datetime.min)
|
||||
|
|
@ -140,6 +185,25 @@ class DateQueryConstructTest(unittest.TestCase):
|
|||
with self.assertRaises(InvalidQueryArgumentValueError):
|
||||
DateQuery('added', q)
|
||||
|
||||
def test_datetime_uppercase_t_separator(self):
|
||||
date_query = DateQuery('added', '2000-01-01T12')
|
||||
self.assertEqual(date_query.interval.start, datetime(2000, 1, 1, 12))
|
||||
self.assertEqual(date_query.interval.end, datetime(2000, 1, 1, 13))
|
||||
|
||||
def test_datetime_lowercase_t_separator(self):
|
||||
date_query = DateQuery('added', '2000-01-01t12')
|
||||
self.assertEqual(date_query.interval.start, datetime(2000, 1, 1, 12))
|
||||
self.assertEqual(date_query.interval.end, datetime(2000, 1, 1, 13))
|
||||
|
||||
def test_datetime_space_separator(self):
|
||||
date_query = DateQuery('added', '2000-01-01 12')
|
||||
self.assertEqual(date_query.interval.start, datetime(2000, 1, 1, 12))
|
||||
self.assertEqual(date_query.interval.end, datetime(2000, 1, 1, 13))
|
||||
|
||||
def test_datetime_invalid_separator(self):
|
||||
with self.assertRaises(InvalidQueryArgumentValueError):
|
||||
DateQuery('added', '2000-01-01x12')
|
||||
|
||||
|
||||
def suite():
|
||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||
|
|
|
|||
Loading…
Reference in a new issue