mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-02-01 08:48:01 +00:00
refactor(time_format): enhance duration parsing with Enum and dynamic regex
- Introduced `ParseDurationUnitFormat` Enum for cleaner unit handling. - Improved `parse_duration` to support case-insensitive matching and dynamic error messages. - Added detailed docstrings for better clarity and usability. - Refactored and added testcases
This commit is contained in:
parent
c09a0ebeb2
commit
14bf5adadb
@ -81,24 +81,42 @@ class TimeFormat(unittest.TestCase, TimezoneMixin):
|
|||||||
DAY = 24*60*60
|
DAY = 24*60*60
|
||||||
MONTH = 31*DAY
|
MONTH = 31*DAY
|
||||||
YEAR = 365*DAY
|
YEAR = 365*DAY
|
||||||
|
|
||||||
|
# seconds
|
||||||
self.failUnlessEqual(p("1s"), 1)
|
self.failUnlessEqual(p("1s"), 1)
|
||||||
|
self.failUnlessEqual(p("12 s"), 12)
|
||||||
|
self.failUnlessEqual(p("333second"), 333)
|
||||||
|
self.failUnlessEqual(p(" 333 second "), 333)
|
||||||
|
self.failUnlessEqual(p("5 seconds"), 5)
|
||||||
|
self.failUnlessEqual(p("60 SECONDS"), 60)
|
||||||
self.failUnlessEqual(p("86400s"), DAY)
|
self.failUnlessEqual(p("86400s"), DAY)
|
||||||
|
|
||||||
|
# days
|
||||||
self.failUnlessEqual(p("1 day"), DAY)
|
self.failUnlessEqual(p("1 day"), DAY)
|
||||||
self.failUnlessEqual(p("2 days"), 2*DAY)
|
self.failUnlessEqual(p("2 days"), 2*DAY)
|
||||||
self.failUnlessEqual(p("3 months"), 3*MONTH)
|
self.failUnlessEqual(p("5days"), 5*DAY)
|
||||||
self.failUnlessEqual(p("4 mo"), 4*MONTH)
|
|
||||||
self.failUnlessEqual(p("5 years"), 5*YEAR)
|
|
||||||
e = self.failUnlessRaises(ValueError, p, "123")
|
|
||||||
self.failUnlessIn("no unit (like day, month, or year) in '123'",
|
|
||||||
str(e))
|
|
||||||
self.failUnlessEqual(p("7days"), 7*DAY)
|
self.failUnlessEqual(p("7days"), 7*DAY)
|
||||||
self.failUnlessEqual(p("31day"), 31*DAY)
|
self.failUnlessEqual(p("31day"), 31*DAY)
|
||||||
self.failUnlessEqual(p("60 days"), 60*DAY)
|
self.failUnlessEqual(p("60 days"), 60*DAY)
|
||||||
|
self.failUnlessEqual(p("70 DAYS"), 70*DAY)
|
||||||
|
|
||||||
|
# months
|
||||||
|
self.failUnlessEqual(p("4 mo"), 4*MONTH)
|
||||||
self.failUnlessEqual(p("2mo"), 2*MONTH)
|
self.failUnlessEqual(p("2mo"), 2*MONTH)
|
||||||
self.failUnlessEqual(p("3 month"), 3*MONTH)
|
self.failUnlessEqual(p("3 month"), 3*MONTH)
|
||||||
|
self.failUnlessEqual(p("3 months"), 3*MONTH)
|
||||||
|
|
||||||
|
# years
|
||||||
|
self.failUnlessEqual(p("5 years"), 5*YEAR)
|
||||||
|
self.failUnlessEqual(p("8 year"), 8*YEAR)
|
||||||
self.failUnlessEqual(p("2years"), 2*YEAR)
|
self.failUnlessEqual(p("2years"), 2*YEAR)
|
||||||
|
self.failUnlessEqual(p("11YEARS"), 11*YEAR)
|
||||||
|
|
||||||
|
# errors
|
||||||
|
e = self.failUnlessRaises(ValueError, p, "123")
|
||||||
|
self.failUnlessIn("No valid unit in",str(e))
|
||||||
e = self.failUnlessRaises(ValueError, p, "2kumquats")
|
e = self.failUnlessRaises(ValueError, p, "2kumquats")
|
||||||
self.failUnlessIn("no unit (like day, month, or year) in '2kumquats'", str(e))
|
self.failUnlessIn("No valid unit in", str(e))
|
||||||
|
|
||||||
def test_parse_date(self):
|
def test_parse_date(self):
|
||||||
p = time_format.parse_date
|
p = time_format.parse_date
|
||||||
|
@ -6,8 +6,26 @@ http://www.cl.cam.ac.uk/~mgk25/iso-time.html
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import calendar, datetime, re, time
|
import calendar, datetime, re, time
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class ParseDurationUnitFormat(Enum):
|
||||||
|
SECONDS0 = "s"
|
||||||
|
SECONDS1 = "second"
|
||||||
|
SECONDS2 = "seconds"
|
||||||
|
DAYS0 = "day"
|
||||||
|
DAYS1 = "days"
|
||||||
|
MONTHS0 = "mo"
|
||||||
|
MONTHS1 = "month"
|
||||||
|
MONTHS2 = "months"
|
||||||
|
YEARS0 = "year"
|
||||||
|
YEARS1 = "years"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list_values(cls):
|
||||||
|
return list(map(lambda c: c.value, cls))
|
||||||
|
|
||||||
|
|
||||||
def format_time(t):
|
def format_time(t):
|
||||||
return time.strftime("%Y-%m-%d %H:%M:%S", t)
|
return time.strftime("%Y-%m-%d %H:%M:%S", t)
|
||||||
@ -50,32 +68,53 @@ def iso_utc_time_to_seconds(isotime, _conversion_re=re.compile(r"(?P<year>\d{4})
|
|||||||
|
|
||||||
return calendar.timegm( (year, month, day, hour, minute, second, 0, 1, 0) ) + subsecfloat
|
return calendar.timegm( (year, month, day, hour, minute, second, 0, 1, 0) ) + subsecfloat
|
||||||
|
|
||||||
|
|
||||||
def parse_duration(s):
|
def parse_duration(s):
|
||||||
orig = s
|
"""
|
||||||
unit = None
|
Parses a duration string and converts it to seconds. The unit format is case insensitive
|
||||||
|
|
||||||
|
Args:
|
||||||
|
s (str): The duration string to parse. Expected format: `<number><unit>`
|
||||||
|
where `unit` can be one of the values defined in `ParseDurationUnitFormat`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: The duration in seconds.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the input string does not match the expected format or contains invalid units.
|
||||||
|
"""
|
||||||
SECOND = 1
|
SECOND = 1
|
||||||
DAY = 24*60*60
|
DAY = 24*60*60
|
||||||
MONTH = 31*DAY
|
MONTH = 31*DAY
|
||||||
YEAR = 365*DAY
|
YEAR = 365*DAY
|
||||||
if s.endswith("s"):
|
time_map = {
|
||||||
unit = SECOND
|
ParseDurationUnitFormat.SECONDS0: SECOND,
|
||||||
s = s[:-1]
|
ParseDurationUnitFormat.SECONDS1: SECOND,
|
||||||
elif s.endswith("day"):
|
ParseDurationUnitFormat.SECONDS2: SECOND,
|
||||||
unit = DAY
|
ParseDurationUnitFormat.DAYS0: DAY,
|
||||||
s = s[:-len("day")]
|
ParseDurationUnitFormat.DAYS1: DAY,
|
||||||
elif s.endswith("month"):
|
ParseDurationUnitFormat.MONTHS0: MONTH,
|
||||||
unit = MONTH
|
ParseDurationUnitFormat.MONTHS1: MONTH,
|
||||||
s = s[:-len("month")]
|
ParseDurationUnitFormat.MONTHS2: MONTH,
|
||||||
elif s.endswith("mo"):
|
ParseDurationUnitFormat.YEARS0: YEAR,
|
||||||
unit = MONTH
|
ParseDurationUnitFormat.YEARS1: YEAR,
|
||||||
s = s[:-len("mo")]
|
}
|
||||||
elif s.endswith("year"):
|
|
||||||
unit = YEAR
|
# Build a regex pattern dynamically from the list of valid values
|
||||||
s = s[:-len("YEAR")]
|
unit_pattern = "|".join(re.escape(unit) for unit in ParseDurationUnitFormat.list_values())
|
||||||
else:
|
pattern = rf"^\s*(\d+)\s*({unit_pattern})\s*$"
|
||||||
raise ValueError("no unit (like s, day, mo, month, or year) in '%s'" % orig)
|
|
||||||
s = s.strip()
|
# case-insensitive regex matching
|
||||||
return int(s) * unit
|
match = re.match(pattern, s, re.IGNORECASE)
|
||||||
|
if not match:
|
||||||
|
# Generate dynamic error message
|
||||||
|
valid_units = ", ".join(f"'{value}'" for value in ParseDurationUnitFormat.list_values())
|
||||||
|
raise ValueError(f"No valid unit in '{s}'. Expected one of: ({valid_units})")
|
||||||
|
|
||||||
|
number = int(match.group(1)) # Extract the numeric value
|
||||||
|
unit = match.group(2).lower() # Extract the unit & normalize the unit to lowercase
|
||||||
|
|
||||||
|
return number * time_map[unit]
|
||||||
|
|
||||||
def parse_date(s):
|
def parse_date(s):
|
||||||
# return seconds-since-epoch for the UTC midnight that starts the given
|
# return seconds-since-epoch for the UTC midnight that starts the given
|
||||||
|
Loading…
x
Reference in New Issue
Block a user