_utils.py
Python script text executable Python script, ASCII text executable
1import os 2import shutil 3import contextlib 4import typing 5from datetime import datetime 6 7__all__ = [ 8"_no_date_constructor", 9"_in_directory", 10"_delete_directory_contents", 11"_parse_date_string", 12] 13 14 15def _no_date_constructor(loader, node): 16"""Function to prevent the YAML loader from converting dates, keeping them as strings, 17so they can be parsed in a more lenient way. 18""" 19value = loader.construct_scalar(node) 20return value 21 22 23@contextlib.contextmanager 24def _in_directory(directory): 25"""Execute a block of code in a different directory. 26 27:param directory: The directory to change to. 28""" 29cwd = os.getcwd() 30os.chdir(directory) 31try: 32yield 33finally: 34os.chdir(cwd) 35 36 37def _delete_directory_contents(directory, dont_delete: typing.Optional[list[str]] = None): 38"""Delete all files and directories in a directory recursively, 39but not the directory itself. 40 41:param directory: The directory to clear. 42:param dont_delete: A list of files and directories to not delete. 43""" 44for root, dirs, files in os.walk(directory): 45for file in files: 46if file not in dont_delete: 47os.remove(os.path.join(root, file)) 48for dir in dirs: 49if dir not in dont_delete: 50shutil.rmtree(os.path.join(root, dir)) 51 52 53def _parse_date_string(date_string): 54"""Parse a date/time string into a datetime object. Supports multiple unambiguous formats. 55 56:param date_string: The date/time string to parse. 57:return: A datetime object representing the date/time string. 58""" 59def split_date_and_time(date_string): 60"""Split a date/time string into a date string and a time string. 61 62:param date_string: The date/time string to split. 63:return: A tuple containing the date and time strings. 64""" 65if ":" not in date_string: 66return date_string, "00:00:00" 67 68elements = date_string.partition(":") 69partition_character = " " 70if " " not in date_string: 71partition_character = "-" 72if "-" not in date_string: 73partition_character = "T" 74 75date = elements[0].rpartition(partition_character)[0].strip() 76time = elements[0].rpartition(partition_character)[2].strip() + elements[1] + elements[2].strip() 77time = time.removeprefix("T").removesuffix("Z") 78 79return date, time 80 81time_formats = [ 82# 24-hour ISO 83"%H:%M:%S", 84"%H:%M", 85"%H", 86# Single digit hour 87"-%H:%M:%S", 88"-%H:%M", 89"-%H", 90# 12-hour (AM/PM) 91"%I:%M:%S %p", 92"%I:%M %p", 93"%I %p", 94# Single digit 12-hour 95"-%I:%M:%S %p", 96"-%I:%M %p", 97"-%I %p", 98] 99 100date_formats = [ 101# ISO formats 102"%Y-%m-%d", 103"%y-%m-%d", 104# European formats 105"%d.%m.%Y", 106"%d.%m.%y", 107# American formats 108"%m/%d/%Y", 109"%m/%d/%y", 110# Text-based European formats 111"%d %B %Y", 112"%d %b %Y", 113"%d %B, %Y", 114"%d %b, %Y", 115# Text-based American formats 116"%B %d %Y", 117"%b %d %Y", 118"%B %d, %Y", 119"%b %d, %Y", 120# ISO weekly calendar 121"%G-W%V-%u", 122] 123 124date, time = split_date_and_time(date_string) 125 126time_object = datetime.min.time() 127date_object = datetime.min.date() 128 129for time_format in time_formats: 130try: 131time_object = datetime.strptime(time, time_format) 132except ValueError: 133pass 134for date_format in date_formats: 135try: 136date_object = datetime.strptime(date, date_format) 137except ValueError: 138pass 139 140return datetime.combine(date_object, time_object.time()) 141