mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2024-12-14 23:32:33 +00:00
[utils] Add datetime_from_str
to parse relative time (#221)
and `datetime_add_months` to accurately add/subtract months Authored by: colethedj
This commit is contained in:
parent
c24ce07a84
commit
9e62f283ff
2 changed files with 81 additions and 20 deletions
|
@ -23,6 +23,7 @@ from yt_dlp.utils import (
|
||||||
clean_html,
|
clean_html,
|
||||||
clean_podcast_url,
|
clean_podcast_url,
|
||||||
date_from_str,
|
date_from_str,
|
||||||
|
datetime_from_str,
|
||||||
DateRange,
|
DateRange,
|
||||||
detect_exe_version,
|
detect_exe_version,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
|
@ -311,8 +312,18 @@ class TestUtil(unittest.TestCase):
|
||||||
self.assertEqual(date_from_str('yesterday'), date_from_str('now-1day'))
|
self.assertEqual(date_from_str('yesterday'), date_from_str('now-1day'))
|
||||||
self.assertEqual(date_from_str('now+7day'), date_from_str('now+1week'))
|
self.assertEqual(date_from_str('now+7day'), date_from_str('now+1week'))
|
||||||
self.assertEqual(date_from_str('now+14day'), date_from_str('now+2week'))
|
self.assertEqual(date_from_str('now+14day'), date_from_str('now+2week'))
|
||||||
self.assertEqual(date_from_str('now+365day'), date_from_str('now+1year'))
|
self.assertEqual(date_from_str('20200229+365day'), date_from_str('20200229+1year'))
|
||||||
self.assertEqual(date_from_str('now+30day'), date_from_str('now+1month'))
|
self.assertEqual(date_from_str('20210131+28day'), date_from_str('20210131+1month'))
|
||||||
|
|
||||||
|
def test_datetime_from_str(self):
|
||||||
|
self.assertEqual(datetime_from_str('yesterday', precision='day'), datetime_from_str('now-1day', precision='auto'))
|
||||||
|
self.assertEqual(datetime_from_str('now+7day', precision='day'), datetime_from_str('now+1week', precision='auto'))
|
||||||
|
self.assertEqual(datetime_from_str('now+14day', precision='day'), datetime_from_str('now+2week', precision='auto'))
|
||||||
|
self.assertEqual(datetime_from_str('20200229+365day', precision='day'), datetime_from_str('20200229+1year', precision='auto'))
|
||||||
|
self.assertEqual(datetime_from_str('20210131+28day', precision='day'), datetime_from_str('20210131+1month', precision='auto'))
|
||||||
|
self.assertEqual(datetime_from_str('20210131+59day', precision='day'), datetime_from_str('20210131+2month', precision='auto'))
|
||||||
|
self.assertEqual(datetime_from_str('now+1day', precision='hour'), datetime_from_str('now+24hours', precision='auto'))
|
||||||
|
self.assertEqual(datetime_from_str('now+23hours', precision='hour'), datetime_from_str('now+23hours', precision='auto'))
|
||||||
|
|
||||||
def test_daterange(self):
|
def test_daterange(self):
|
||||||
_20century = DateRange("19000101", "20000101")
|
_20century = DateRange("19000101", "20000101")
|
||||||
|
|
|
@ -3052,33 +3052,83 @@ def subtitles_filename(filename, sub_lang, sub_format, expected_real_ext=None):
|
||||||
return replace_extension(filename, sub_lang + '.' + sub_format, expected_real_ext)
|
return replace_extension(filename, sub_lang + '.' + sub_format, expected_real_ext)
|
||||||
|
|
||||||
|
|
||||||
def date_from_str(date_str):
|
def datetime_from_str(date_str, precision='auto', format='%Y%m%d'):
|
||||||
"""
|
"""
|
||||||
Return a datetime object from a string in the format YYYYMMDD or
|
Return a datetime object from a string in the format YYYYMMDD or
|
||||||
(now|today)[+-][0-9](day|week|month|year)(s)?"""
|
(now|today|date)[+-][0-9](microsecond|second|minute|hour|day|week|month|year)(s)?
|
||||||
today = datetime.date.today()
|
|
||||||
|
format: string date format used to return datetime object from
|
||||||
|
precision: round the time portion of a datetime object.
|
||||||
|
auto|microsecond|second|minute|hour|day.
|
||||||
|
auto: round to the unit provided in date_str (if applicable).
|
||||||
|
"""
|
||||||
|
auto_precision = False
|
||||||
|
if precision == 'auto':
|
||||||
|
auto_precision = True
|
||||||
|
precision = 'microsecond'
|
||||||
|
today = datetime_round(datetime.datetime.now(), precision)
|
||||||
if date_str in ('now', 'today'):
|
if date_str in ('now', 'today'):
|
||||||
return today
|
return today
|
||||||
if date_str == 'yesterday':
|
if date_str == 'yesterday':
|
||||||
return today - datetime.timedelta(days=1)
|
return today - datetime.timedelta(days=1)
|
||||||
match = re.match(r'(now|today)(?P<sign>[+-])(?P<time>\d+)(?P<unit>day|week|month|year)(s)?', date_str)
|
match = re.match(
|
||||||
|
r'(?P<start>.+)(?P<sign>[+-])(?P<time>\d+)(?P<unit>microsecond|second|minute|hour|day|week|month|year)(s)?',
|
||||||
|
date_str)
|
||||||
if match is not None:
|
if match is not None:
|
||||||
sign = match.group('sign')
|
start_time = datetime_from_str(match.group('start'), precision, format)
|
||||||
time = int(match.group('time'))
|
time = int(match.group('time')) * (-1 if match.group('sign') == '-' else 1)
|
||||||
if sign == '-':
|
|
||||||
time = -time
|
|
||||||
unit = match.group('unit')
|
unit = match.group('unit')
|
||||||
# A bad approximation?
|
if unit == 'month' or unit == 'year':
|
||||||
if unit == 'month':
|
new_date = datetime_add_months(start_time, time * 12 if unit == 'year' else time)
|
||||||
unit = 'day'
|
unit = 'day'
|
||||||
time *= 30
|
else:
|
||||||
elif unit == 'year':
|
if unit == 'week':
|
||||||
unit = 'day'
|
unit = 'day'
|
||||||
time *= 365
|
time *= 7
|
||||||
unit += 's'
|
delta = datetime.timedelta(**{unit + 's': time})
|
||||||
delta = datetime.timedelta(**{unit: time})
|
new_date = start_time + delta
|
||||||
return today + delta
|
if auto_precision:
|
||||||
return datetime.datetime.strptime(date_str, '%Y%m%d').date()
|
return datetime_round(new_date, unit)
|
||||||
|
return new_date
|
||||||
|
|
||||||
|
return datetime_round(datetime.datetime.strptime(date_str, format), precision)
|
||||||
|
|
||||||
|
|
||||||
|
def date_from_str(date_str, format='%Y%m%d'):
|
||||||
|
"""
|
||||||
|
Return a datetime object from a string in the format YYYYMMDD or
|
||||||
|
(now|today|date)[+-][0-9](microsecond|second|minute|hour|day|week|month|year)(s)?
|
||||||
|
|
||||||
|
format: string date format used to return datetime object from
|
||||||
|
"""
|
||||||
|
return datetime_from_str(date_str, precision='microsecond', format=format).date()
|
||||||
|
|
||||||
|
|
||||||
|
def datetime_add_months(dt, months):
|
||||||
|
"""Increment/Decrement a datetime object by months."""
|
||||||
|
month = dt.month + months - 1
|
||||||
|
year = dt.year + month // 12
|
||||||
|
month = month % 12 + 1
|
||||||
|
day = min(dt.day, calendar.monthrange(year, month)[1])
|
||||||
|
return dt.replace(year, month, day)
|
||||||
|
|
||||||
|
|
||||||
|
def datetime_round(dt, precision='day'):
|
||||||
|
"""
|
||||||
|
Round a datetime object's time to a specific precision
|
||||||
|
"""
|
||||||
|
if precision == 'microsecond':
|
||||||
|
return dt
|
||||||
|
|
||||||
|
unit_seconds = {
|
||||||
|
'day': 86400,
|
||||||
|
'hour': 3600,
|
||||||
|
'minute': 60,
|
||||||
|
'second': 1,
|
||||||
|
}
|
||||||
|
roundto = lambda x, n: ((x + n / 2) // n) * n
|
||||||
|
timestamp = calendar.timegm(dt.timetuple())
|
||||||
|
return datetime.datetime.utcfromtimestamp(roundto(timestamp, unit_seconds[precision]))
|
||||||
|
|
||||||
|
|
||||||
def hyphenate_date(date_str):
|
def hyphenate_date(date_str):
|
||||||
|
|
Loading…
Reference in a new issue