For those wanting a more elegant catch all solution I created a small module so you don't have to. Accepts normal format strings but uses brackets functioning as optionals. Using some simple counter algorithm to capture the bracketed "optionals" and using the combinations library to build a list of possible combinations of the input date_format string.
It's not efficient at finding all the possible combinations so I would input and store the format list before running any intensive logic but it should be a nice catch all solution for anyone to use.
Some things to note, if you don't format the brackets properly, you'll get an ugly IndexError. Feel free to create your own exception for missing closing brackets if you feel the need. I also don't know that I adequately handled all nested bracket cases. I did test extensively though so I'm pretty sure this will cover all the bases. Of course if you want to use something other than [] brackets, I gave you some easy to change attribute variables.
from datetime import datetime
from itertools import combinations
opening_char = '['
closing_char = ']'
def parse_datetime(date_string, date_formats):
for format_string in date_formats:
try:
parsed_date = datetime.strptime(date_string, format_string)
return parsed_date
except ValueError:
continue
print(f"Unable to parse date with any given format for string: {date_string}")
return None
def _extract_optional_components(format_string):
if opening_char in format_string:
sub_strings = _get_bracketed_strings(format_string)
for s in sub_strings:
s.replace(opening_char, '')
s.replace(closing_char, '')
return sub_strings
else:
return []
def _get_bracketed_strings(input_string):
sub_strings = []
for i, char in enumerate(input_string):
if char == opening_char:
openpos = i
closepos = openpos
counter = 1
while counter > 0:
closepos += 1
c = format_string[closepos]
if c == opening_char:
counter += 1
elif c == closing_char:
counter -= 1
sub_strings.append(input_string[openpos + 1:closepos])
return sub_strings
def _generate_date_formats(format_string):
optional_components = _extract_optional_components(format_string)
num_optionals = len(optional_components)
all_combinations = []
for r in range(num_optionals + 1):
for combination in combinations(range(num_optionals), r):
all_combinations.append(combination)
output_formats = []
for combination in all_combinations:
new_format = format_string
for i in range(num_optionals):
if i in combination:
new_format = new_format.replace(f'[{optional_components[i]}]', optional_components[i])
else:
new_format = new_format.replace(f'[{optional_components[i]}]', '')
output_formats.append(new_format)
return output_formats
if __name__ == "__main__":
# Example usage
format_string = "%Y-%m-%d[T%H:%M:%S[.%f]][Z]"
optional_format_list = _generate_date_formats(format_string)
date_string1 = "2023-06-16T03:09:23.155Z"
date_string2 = "2023-06-16T02:53:18Z"
date_string3 = "2023-06-16"
datetime_obj1 = parse_datetime(date_string1, optional_format_list)
datetime_obj2 = parse_datetime(date_string2, optional_format_list)
datetime_obj3 = parse_datetime(date_string3, optional_format_list)
print(datetime_obj1) # 2023-06-16 03:09:23.155000+00:00
print(datetime_obj2) # 2023-06-16 02:53:18+00:00
print(datetime_obj3) # 2023-06-16 00:00:00+00:00