fx: Nextcloud migration fails to parse null times (#2485)

* support null or custom strings

* made time parsing more robust using isodate

* bark bark
This commit is contained in:
Michael Genson 2023-08-09 21:52:49 -05:00 committed by GitHub
parent dd947c316a
commit e55258c5be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 39 additions and 14 deletions

View File

@ -1,9 +1,12 @@
import re
import tempfile
import zipfile
from dataclasses import dataclass
from datetime import timedelta
from pathlib import Path
from typing import cast
import isodate
from isodate.isoerror import ISO8601Error
from slugify import slugify
from ._migration_base import BaseMigrator
@ -75,22 +78,43 @@ class NextcloudMigrator(BaseMigrator):
import_image(nc_dir.image, recipe_id)
def parse_time(time: str) -> str:
"""Parses a Nextcloud time string in the format 'PT{hours}H{minutes}M{seconds}S'"""
def parse_time(time: str | None) -> str:
"""
Parses an ISO8601 duration string
https://en.wikipedia.org/wiki/ISO_8601#Durations
"""
if not time:
return ""
if time[0] == "P":
try:
delta = isodate.parse_duration(time)
if not isinstance(delta, timedelta):
return time
except ISO8601Error:
return time
# TODO: make singular and plural translatable
hours = {"singular": "hour", "plural": "hours", "exp": r"\d+(?=H)"}
minutes = {"singular": "minute", "plural": "minutes", "exp": r"\d+(?=M)"}
seconds = {"singular": "second", "plural": "seconds", "exp": r"\d+(?=S)"}
time_part_map = {
"days": {"singular": "day", "plural": "days"},
"hours": {"singular": "hour", "plural": "hours"},
"minutes": {"singular": "minute", "plural": "minutes"},
"seconds": {"singular": "second", "plural": "seconds"},
}
delta = cast(timedelta, delta)
time_part_map["days"]["value"] = delta.days
time_part_map["hours"]["value"] = delta.seconds // 3600
time_part_map["minutes"]["value"] = (delta.seconds // 60) % 60
time_part_map["seconds"]["value"] = delta.seconds % 60
return_strings: list[str] = []
for time_part in [hours, minutes, seconds]:
val_search = re.search(time_part["exp"], time)
if not val_search:
for value_map in time_part_map.values():
if not (value := value_map["value"]):
continue
val = val_search.group()
if val == "0":
continue
return_strings.append(f'{val} {time_part["singular"] if val == "1" else time_part["plural"]}')
unit_key = "singular" if value == 1 else "plural"
return_strings.append(f"{value} {value_map[unit_key]}")
return " ".join(return_strings) if return_strings else time

2
poetry.lock generated
View File

@ -2935,4 +2935,4 @@ pgsql = ["psycopg2-binary"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "e25136f573af5b1b7ba61747a5bea6a4f2eb48f39b06e345cba03b14808d9258"
content-hash = "6ec46a82cad73c78013d1729df179a3fb5c4e12eb2a11ca04b2412d6b8e5b25f"

View File

@ -42,6 +42,7 @@ requests = "^2.25.1"
tzdata = "^2022.7"
uvicorn = {extras = ["standard"], version = "^0.21.0"}
beautifulsoup4 = "^4.11.2"
isodate = "^0.6.1"
[tool.poetry.group.dev.dependencies]
black = "^23.1.0"