import datetime
from json import JSONEncoder


class DateTimeEncoder(JSONEncoder):
    """Encodes datetime objects into ISO format in json.dump methods"""

    def default(self, obj):
        if isinstance(obj, (datetime.date, datetime.datetime)):
            return obj.isoformat()


def str_to_datetime(dt_str:str):
    """Tries several several ISO-based datetime formats to get datetime object."""

    formats = [
        "%Y-%m-%dT%H:%M:%S%z",    # ISO with UTC offset
        "%Y-%m-%dT%H:%M:%S",      # ISO with no offset
        "%Y-%m-%dT%H:%M:%S.%f",   # ISO with microsec and no offset
        "%Y-%m-%dT%H:%M:%S.%f%z", # ISO with microsec and UTC offset
        "%Y-%m-%d %H:%M:%S%z",    # MySQL with UTC offset
        "%Y-%m-%d %H:%M:%S",      # MySQL with no offset
        "%Y-%m-%d %H:%M:%S.%f",   # MySQL with microsec and no offset
        "%Y-%m-%d %H:%M:%S.%f%z", # MySQL with microsec and UTC offset
        "%m/%d/%Y",               # US date with no time and 4-digit year
        "%m/%d/%y",               # US date with no time and 2-digit year
        "%Y-%m-%d",               # ISO date with no time and 4-digit year
    ]

    dt = None
    for fmt in formats:
        try:
            dt = datetime.datetime.strptime(dt_str, fmt)
        except ValueError:
            continue

    # Couldn't parse it? Then make this function act like strptime()
    if dt is None:
        raise ValueError("time data '{0}' does not match any known format".format(dt_str))

    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=datetime.timezone.utc)

    return dt
