Skip to content

date_convertions

shortfx.fxDate.date_convertions

Date and time conversion module.

This module provides comprehensive functions for converting between different date/time formats including: datetime objects, strings, timestamps, ISO formats, timezones, Julian dates, Windows FILETIME, and Active Directory formats. All functions handle timezone-aware and timezone-naive datetime objects appropriately.

Functions

ad_format_to_datetime(p_ad_date_str: str) -> datetime

Convierte una cadena de fecha/hora en formato 'YYYYMMDDHHMMSS.0Z' (usado en Active Directory) a un objeto datetime de Python (timezone-aware y en UTC).

Parameters:

Name Type Description Default
p_ad_date_str str

La cadena de fecha/hora en formato 'YYYYMMDDHHMMSS.0Z'.

required

Returns:

Name Type Description
datetime datetime

Un objeto datetime que representa la fecha y hora en UTC.

Raises:

Type Description
ValueError

Si la cadena de entrada no coincide con el formato esperado.

TypeError

Si la entrada no es una cadena.

Source code in shortfx/fxDate/date_convertions.py
def ad_format_to_datetime(p_ad_date_str: str) -> datetime:
    """
    Convierte una cadena de fecha/hora en formato 'YYYYMMDDHHMMSS.0Z' (usado en Active Directory)
    a un objeto datetime de Python (timezone-aware y en UTC).

    Args:
        p_ad_date_str (str): La cadena de fecha/hora en formato 'YYYYMMDDHHMMSS.0Z'.

    Returns:
        datetime: Un objeto datetime que representa la fecha y hora en UTC.

    Raises:
        ValueError: Si la cadena de entrada no coincide con el formato esperado.
        TypeError: Si la entrada no es una cadena.
    """
    if not isinstance(p_ad_date_str, str):
        raise TypeError("La entrada debe ser una cadena.")

    # El formato esperado es "YYYYMMDDHHMMSS.0Z"
    # Quitamos ".0Z" y usamos el formato de strptime
    # Aseguramos que termina en ".0Z" para una validación más estricta
    if not p_ad_date_str.endswith(".0Z"):
        raise ValueError(f"Formato de cadena incorrecto. Se esperaba 'YYYYMMDDHHMMSS.0Z'. "
                         f"Recibido: '{p_ad_date_str}'")

    # Quitamos los últimos 3 caracteres (".0Z") para que strptime pueda parsear
    clean_date_str = p_ad_date_str[:-3]

    # Formato de strptime para "YYYYMMDDHHMMSS"
    format_strptime = "%Y%m%d%H%M%S"

    try:
        # Parsear la cadena a un datetime naive
        dt_naive = datetime.strptime(clean_date_str, format_strptime)
        # Asignar la zona horaria UTC
        dt_utc = dt_naive.replace(tzinfo=timezone.utc)
        return dt_utc
    except ValueError:
        # Esto capturaría errores si la parte de la fecha/hora no es válida
        raise ValueError(f"No se pudo parsear la fecha/hora '{p_ad_date_str}'. "
                         f"Asegúrate de que los componentes numéricos sean válidos.")

convert_timezone(dt: datetime, from_tz: str, to_tz: str) -> datetime

Converts a datetime between two IANA timezones.

If the input datetime is naive it is assumed to belong to from_tz.

Parameters:

Name Type Description Default
dt datetime

The datetime to convert.

required
from_tz str

Source timezone name (e.g. "UTC", "US/Eastern").

required
to_tz str

Target timezone name (e.g. "Europe/Madrid").

required

Returns:

Type Description
datetime

A timezone-aware datetime in the target timezone.

Raises:

Type Description
TypeError

If dt is not a datetime object.

Example

from datetime import datetime convert_timezone(datetime(2026, 4, 4, 12, 0), "UTC", "Europe/Madrid") datetime.datetime(2026, 4, 4, 14, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Madrid'))

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def convert_timezone(
    dt: datetime,
    from_tz: str,
    to_tz: str,
) -> datetime:
    """Converts a datetime between two IANA timezones.

    If the input datetime is naive it is assumed to belong to ``from_tz``.

    Args:
        dt: The datetime to convert.
        from_tz: Source timezone name (e.g. ``"UTC"``, ``"US/Eastern"``).
        to_tz: Target timezone name (e.g. ``"Europe/Madrid"``).

    Returns:
        A timezone-aware datetime in the target timezone.

    Raises:
        TypeError: If dt is not a datetime object.

    Example:
        >>> from datetime import datetime
        >>> convert_timezone(datetime(2026, 4, 4, 12, 0), "UTC", "Europe/Madrid")
        datetime.datetime(2026, 4, 4, 14, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Madrid'))

    Complexity: O(1)
    """
    if not isinstance(dt, datetime):
        raise TypeError("Input must be a datetime object.")

    source = zoneinfo.ZoneInfo(from_tz)
    target = zoneinfo.ZoneInfo(to_tz)

    # Attach source tz if naive
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=source)

    return dt.astimezone(target)

date_to_excel_serial(d: Union[date, datetime]) -> int

Converts a Python date to an Excel serial date number.

Description

Returns the serial number compatible with Excel's 1900 date system. Accounts for the Lotus 1-2-3 leap year bug so that round-tripping through excel_serial_to_date is consistent.

Parameters:

Name Type Description Default
d Union[date, datetime]

A date or datetime instance.

required

Returns:

Type Description
int

Excel serial date number (integer).

Raises:

Type Description
TypeError

If d is not a date or datetime.

ValueError

If d is before 1900-01-01.

Usage Example

from datetime import date date_to_excel_serial(date(2023, 1, 1)) 44927

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def date_to_excel_serial(d: Union[date, datetime]) -> int:
    """Converts a Python date to an Excel serial date number.

    Description:
        Returns the serial number compatible with Excel's 1900 date
        system.  Accounts for the Lotus 1-2-3 leap year bug so that
        round-tripping through ``excel_serial_to_date`` is consistent.

    Args:
        d: A ``date`` or ``datetime`` instance.

    Returns:
        Excel serial date number (integer).

    Raises:
        TypeError: If *d* is not a ``date`` or ``datetime``.
        ValueError: If *d* is before 1900-01-01.

    Usage Example:
        >>> from datetime import date
        >>> date_to_excel_serial(date(2023, 1, 1))
        44927

    Complexity: O(1)
    """
    if isinstance(d, datetime):
        d = d.date()

    if not isinstance(d, date):
        raise TypeError("d must be a date or datetime instance")

    epoch = date(1899, 12, 31)

    if d < date(1900, 1, 1):
        raise ValueError("Date must be on or after 1900-01-01")

    delta = (d - epoch).days

    # Adjust for the Lotus 1-2-3 bug (serials > 59 are +1)
    if delta >= 60:
        delta += 1

    return delta

date_to_excel_timestamp(dt_val: datetime) -> float

Converts a datetime to an Excel serial number including fractional day.

Excel serial day 1 = 1900-01-01. The fractional part represents the time of day (e.g. 0.5 = noon).

Parameters:

Name Type Description Default
dt_val datetime

A datetime object.

required

Returns:

Type Description
float

Excel serial number with fractional day component.

Raises:

Type Description
TypeError

If dt_val is not a datetime.

ValueError

If date is before 1900-01-01.

Example

from datetime import datetime date_to_excel_timestamp(datetime(2026, 4, 8, 12, 0, 0)) 46113.5

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def date_to_excel_timestamp(dt_val: datetime) -> float:
    """Converts a datetime to an Excel serial number including fractional day.

    Excel serial day 1 = 1900-01-01. The fractional part represents
    the time of day (e.g. 0.5 = noon).

    Args:
        dt_val: A datetime object.

    Returns:
        Excel serial number with fractional day component.

    Raises:
        TypeError: If dt_val is not a datetime.
        ValueError: If date is before 1900-01-01.

    Example:
        >>> from datetime import datetime
        >>> date_to_excel_timestamp(datetime(2026, 4, 8, 12, 0, 0))
        46113.5

    Complexity: O(1)
    """
    if not isinstance(dt_val, datetime):
        raise TypeError("dt_val must be a datetime")

    base = date(1899, 12, 30)  # Excel epoch (accounting for Lotus 1-2-3 bug)
    dt_date = dt_val.date()

    if dt_date < date(1900, 1, 1):
        raise ValueError("Date must be on or after 1900-01-01")

    day_serial = (dt_date - base).days
    time_fraction = (
        dt_val.hour * 3600 + dt_val.minute * 60 + dt_val.second
    ) / 86400.0
    return day_serial + time_fraction

date_to_human_short(dt_value: Union[date, datetime], language: str = 'en') -> str

Formats a date as a short human-readable string.

Parameters:

Name Type Description Default
dt_value Union[date, datetime]

The date or datetime to format.

required
language str

"en" for "Apr 4, 2026", "es" for "4 abr. 2026".

'en'

Returns:

Type Description
str

A short formatted date string.

Example

from datetime import date date_to_human_short(date(2026, 4, 4), "en") 'Apr 4, 2026' date_to_human_short(date(2026, 4, 4), "es") '4 abr. 2026'

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def date_to_human_short(
    dt_value: Union[date, datetime],
    language: str = "en",
) -> str:
    """Formats a date as a short human-readable string.

    Args:
        dt_value: The date or datetime to format.
        language: ``"en"`` for ``"Apr 4, 2026"``, ``"es"`` for ``"4 abr. 2026"``.

    Returns:
        A short formatted date string.

    Example:
        >>> from datetime import date
        >>> date_to_human_short(date(2026, 4, 4), "en")
        'Apr 4, 2026'
        >>> date_to_human_short(date(2026, 4, 4), "es")
        '4 abr. 2026'

    Complexity: O(1)
    """
    if isinstance(dt_value, datetime):
        d = dt_value.date()
    elif isinstance(dt_value, date):
        d = dt_value
    else:
        raise TypeError("dt_value must be a date or datetime.")

    if language == "es":
        month_name = _MONTHS_ES[d.month - 1]
        return f"{d.day} {month_name}. {d.year}"

    month_name = _MONTHS_EN[d.month - 1]
    return f"{month_name} {d.day}, {d.year}"

date_to_iso_format(date_input: Union[datetime, str], input_format: str = None) -> str

Converts a datetime object to an ISO 8601 formatted string.

ISO 8601 is an international standard for representing dates and times, ensuring clarity and interoperability across different systems and locales. The common format is YYYY-MM-DDTHH:MM:SS.ffffff+HH:MM (date, 'T', time, microseconds, and timezone offset).

Parameters:

Name Type Description Default
date_input Union[datetime, str]

The date to convert. Can be a datetime object (e.g., datetime(2023, 10, 26, 15, 30)) or a string (e.g., "26/10/2023 15:30:00").

required
input_format str

The format code string for date_input if it's a string. This is required if date_input is a string. Example: '%d/%m/%Y %H:%M:%S' for "26/10/2023 15:30:00". Not used if date_input is a datetime object.

None

Returns:

Name Type Description
str str

The date represented as an ISO 8601 formatted string.

Raises:

Type Description
TypeError

If date_input is not a datetime object or a string.

ValueError

If date_input is a string and input_format is not provided, or if the string cannot be parsed with the given format.

Example

from datetime import datetime, timezone

Example with a naive datetime object (no timezone)

date_to_iso_format(datetime(2023, 1, 15, 10, 30, 45, 123456)) '2023-01-15T10:30:45.123456'

dt_utc = datetime(2023, 1, 15, 10, 30, 45, 123456, tzinfo=timezone.utc) date_to_iso_format(dt_utc) '2023-01-15T10:30:45.123456+00:00'

Example with a date-only datetime object

date_to_iso_format(datetime(2024, 7, 20).date()) # Convert to datetime first '2024-07-20T00:00:00'

Example with string input

date_to_iso_format("2023-05-10 08:00:00", "%Y-%m-%d %H:%M:%S") '2023-05-10T08:00:00'

Example of an invalid input (string without format)

try: date_to_iso_format("2023-05-10") except ValueError as e: print(f"Error: {e}")

Expected output: Error: 'input_format' is required when 'date_input' is a string.

Cost: O(1)

Source code in shortfx/fxDate/date_convertions.py
def date_to_iso_format(date_input: Union[datetime, str], input_format: str = None) -> str:
    """Converts a datetime object to an ISO 8601 formatted string.

    ISO 8601 is an international standard for representing dates and times,
    ensuring clarity and interoperability across different systems and locales.
    The common format is YYYY-MM-DDTHH:MM:SS.ffffff+HH:MM (date, 'T', time, microseconds, and timezone offset).

    Args:
        date_input (Union[datetime, str]): The date to convert.
                                            Can be a datetime object (e.g., `datetime(2023, 10, 26, 15, 30)`)
                                            or a string (e.g., `"26/10/2023 15:30:00"`).
        input_format (str, optional): The format code string for `date_input` if it's a string.
                                      This is **required** if `date_input` is a string.
                                      Example: `'%d/%m/%Y %H:%M:%S'` for `"26/10/2023 15:30:00"`.
                                      Not used if `date_input` is a `datetime` object.

    Returns:
        str: The date represented as an ISO 8601 formatted string.

    Raises:
        TypeError: If `date_input` is not a `datetime` object or a string.
        ValueError: If `date_input` is a string and `input_format` is not provided,
                    or if the string cannot be parsed with the given format.

    Example:
        >>> from datetime import datetime, timezone

        >>> # Example with a naive datetime object (no timezone)
        >>> date_to_iso_format(datetime(2023, 1, 15, 10, 30, 45, 123456))
        '2023-01-15T10:30:45.123456'

        >>> # Example with a timezone-aware datetime object (recommended for robustness)
        >>> dt_utc = datetime(2023, 1, 15, 10, 30, 45, 123456, tzinfo=timezone.utc)
        >>> date_to_iso_format(dt_utc)
        '2023-01-15T10:30:45.123456+00:00'

        >>> # Example with a date-only datetime object
        >>> date_to_iso_format(datetime(2024, 7, 20).date()) # Convert to datetime first
        '2024-07-20T00:00:00'

        >>> # Example with string input
        >>> date_to_iso_format("2023-05-10 08:00:00", "%Y-%m-%d %H:%M:%S")
        '2023-05-10T08:00:00'

        >>> # Example of an invalid input (string without format)
        >>> try:
        >>>     date_to_iso_format("2023-05-10")
        >>> except ValueError as e:
        >>>     print(f"Error: {e}")
        # Expected output: Error: 'input_format' is required when 'date_input' is a string.

    Cost: O(1)
    """
    # First, ensure we have a datetime object to work with.
    parsed_dt: datetime
    if isinstance(date_input, str):
        if input_format is None:
            raise ValueError("'input_format' is required when 'date_input' is a string.")
        try:
            parsed_dt = datetime.strptime(date_input, input_format)
        except ValueError as e:
            raise ValueError(f"Could not parse date string '{date_input}' with format '{input_format}'. Error: {e}") from e
    elif isinstance(date_input, datetime):
        parsed_dt = date_input
    elif isinstance(date_input, datetime.date):
        # If it's a date object, convert it to a datetime at midnight
        parsed_dt = datetime(date_input.year, date_input.month, date_input.day)
    else:
        raise TypeError("Input 'date_input' must be a datetime object, date object, or a string.")

    # The datetime object's .isoformat() method handles the conversion directly.
    return parsed_dt.isoformat()

date_to_iso_week_date(d: Union[date, datetime]) -> str

Convert a date to an ISO 8601 week date string.

Description

Returns the date in YYYY-Www-D format, where YYYY is the ISO week-numbering year, ww is the week number (01–53), and D is the day of the week (1=Monday, 7=Sunday).

Parameters:

Name Type Description Default
d Union[date, datetime]

A date or datetime instance.

required

Returns:

Type Description
str

ISO week date string (e.g. '2023-W01-1').

Raises:

Type Description
TypeError

If d is not a date or datetime.

Usage Example

from datetime import date date_to_iso_week_date(date(2023, 1, 2)) '2023-W01-1'

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def date_to_iso_week_date(d: Union[date, datetime]) -> str:
    """Convert a date to an ISO 8601 week date string.

    Description:
        Returns the date in ``YYYY-Www-D`` format, where ``YYYY`` is the
        ISO week-numbering year, ``ww`` is the week number (01–53), and
        ``D`` is the day of the week (1=Monday, 7=Sunday).

    Args:
        d: A ``date`` or ``datetime`` instance.

    Returns:
        ISO week date string (e.g. ``'2023-W01-1'``).

    Raises:
        TypeError: If *d* is not a ``date`` or ``datetime``.

    Usage Example:
        >>> from datetime import date
        >>> date_to_iso_week_date(date(2023, 1, 2))
        '2023-W01-1'

    Complexity: O(1)
    """
    if isinstance(d, datetime):
        d = d.date()

    if not isinstance(d, date):
        raise TypeError("d must be a date or datetime instance")

    iso_year, iso_week, iso_day = d.isocalendar()

    return f"{iso_year}-W{iso_week:02d}-{iso_day}"

date_to_julian(date_input: Union[datetime, str], input_format: Optional[str] = None) -> int

Convierte una fecha estándar a su representación de fecha Juliana (día del año).

Problema/Necesidad del Usuario: Para ciertos sistemas antiguos o en astronomía, las fechas se representan como el número de día del año (ej. el día 32 es el 1 de Febrero). Es necesario convertir fechas comunes a este formato.

Objetivos del Producto: Proporcionar una forma sencilla de obtener el "día del año" para una fecha dada, facilitando la integración con sistemas que utilizan este formato.

Descripción: Dada una fecha (como objeto datetime o cadena de texto), esta función devuelve el número del día dentro de ese año. Enero 1 es el día 1, Febrero 1 es el día 32 (o 33 en un año bisiesto), etc.

Parameters:

Name Type Description Default
date_input Union[datetime, str]

La fecha a convertir. Puede ser un objeto datetime o una cadena de fecha.

required
input_format str

El formato de cadena de fecha (ej. '%Y-%m-%d') si 'date_input' es una cadena. Es obligatorio si 'date_input' es una cadena.

None

Returns:

Name Type Description
int int

El número del día del año (1 a 365 o 366).

Raises:

Type Description
TypeError

Si 'date_input' no es un objeto datetime o una cadena.

ValueError

Si 'date_input' es una cadena y 'input_format' es None, o si la cadena no puede ser parseada con el formato dado.

Example

from datetime import datetime

Ejemplo 1: Enero 1, 2025 (siempre día 1)

date_to_julian(datetime(2025, 1, 1)) 1

Ejemplo 2: Febrero 1, 2025 (año no bisiesto)

date_to_julian(datetime(2025, 2, 1)) 32 # 31 días de Enero + 1 día de Febrero

Ejemplo 3: Marzo 1, 2024 (año bisiesto)

date_to_julian(datetime(2024, 3, 1)) 61 # 31 (Ene) + 29 (Feb 2024) + 1 (Mar) = 61

Ejemplo 4: 31 de Diciembre de un año común

date_to_julian(datetime(2023, 12, 31)) 365

Ejemplo 5: 31 de Diciembre de un año bisiesto

date_to_julian(datetime(2024, 12, 31)) 366

Ejemplo 6: Usando cadena de fecha

date_to_julian("2025-06-15", "%Y-%m-%d") 166 # Día 166 del año 2025

Cost: O(1)

Source code in shortfx/fxDate/date_convertions.py
def date_to_julian(date_input: Union[datetime, str], input_format: Optional[str] = None) -> int:
    """Convierte una fecha estándar a su representación de fecha Juliana (día del año).

    Problema/Necesidad del Usuario: Para ciertos sistemas antiguos o en astronomía,
    las fechas se representan como el número de día del año (ej. el día 32 es el 1 de Febrero).
    Es necesario convertir fechas comunes a este formato.

    Objetivos del Producto: Proporcionar una forma sencilla de obtener el "día del año"
    para una fecha dada, facilitando la integración con sistemas que utilizan este formato.

    Descripción: Dada una fecha (como objeto `datetime` o cadena de texto),
    esta función devuelve el número del día dentro de ese año. Enero 1 es el día 1,
    Febrero 1 es el día 32 (o 33 en un año bisiesto), etc.

    Args:
        date_input (Union[datetime, str]): La fecha a convertir. Puede ser un objeto
                                            `datetime` o una cadena de fecha.
        input_format (str, optional): El formato de cadena de fecha (ej. '%Y-%m-%d')
                                      si 'date_input' es una cadena. Es **obligatorio**
                                      si 'date_input' es una cadena.

    Returns:
        int: El número del día del año (1 a 365 o 366).

    Raises:
        TypeError: Si 'date_input' no es un objeto `datetime` o una cadena.
        ValueError: Si 'date_input' es una cadena y 'input_format' es `None`,
                    o si la cadena no puede ser parseada con el formato dado.

    Example:
        >>> from datetime import datetime

        >>> # Ejemplo 1: Enero 1, 2025 (siempre día 1)
        >>> date_to_julian(datetime(2025, 1, 1))
        1

        >>> # Ejemplo 2: Febrero 1, 2025 (año no bisiesto)
        >>> date_to_julian(datetime(2025, 2, 1))
        32 # 31 días de Enero + 1 día de Febrero

        >>> # Ejemplo 3: Marzo 1, 2024 (año bisiesto)
        >>> date_to_julian(datetime(2024, 3, 1))
        61 # 31 (Ene) + 29 (Feb 2024) + 1 (Mar) = 61

        >>> # Ejemplo 4: 31 de Diciembre de un año común
        >>> date_to_julian(datetime(2023, 12, 31))
        365

        >>> # Ejemplo 5: 31 de Diciembre de un año bisiesto
        >>> date_to_julian(datetime(2024, 12, 31))
        366

        >>> # Ejemplo 6: Usando cadena de fecha
        >>> date_to_julian("2025-06-15", "%Y-%m-%d")
        166 # Día 166 del año 2025

    Cost: O(1)
    """
    # Función auxiliar interna para parsear la entrada de fecha (datetime o string)
    def _parse_date_input_internal(date_val: Union[datetime, str], input_fmt: Optional[str]) -> datetime:
        if isinstance(date_val, str):
            if input_fmt is None:
                raise ValueError("'input_format' is required when 'date_input' is a string.")
            try:
                return datetime.strptime(date_val, input_fmt)
            except ValueError as e:
                raise ValueError(f"Could not parse date string '{date_val}' with format '{input_fmt}'. Error: {e}") from e
        elif isinstance(date_val, datetime):
            return date_val
        else:
            raise TypeError("Input 'date_input' must be a datetime object or a string.")

    # Parsear la fecha de entrada
    parsed_date = _parse_date_input_internal(date_input, input_format)

    # El atributo tm_yday del resultado de .timetuple() da directamente el día del año (1-indexed)
    return parsed_date.timetuple().tm_yday

date_to_string(date_input: datetime, format_code: str = '%Y-%m-%d') -> str

Converts a datetime object into a formatted string.

Description

Formats a datetime object as a string using Python's strftime format codes. Useful for displaying dates in human-readable formats or for system integration requiring specific date string conventions.

Parameters:

Name Type Description Default
date_input datetime

The datetime object to convert to a string.

required
format_code str

The format code string (e.g., '%Y-%m-%d', '%d/%m/%Y %H:%M:%S'). Defaults to '%Y-%m-%d'.

'%Y-%m-%d'

Returns:

Name Type Description
str str

The formatted date string.

Raises:

Type Description
TypeError

If 'date_input' is not a datetime object.

Usage Example

from datetime import datetime from shortfx.fxDate.date_convertions import date_to_string my_date = datetime(2026, 1, 3, 14, 30, 45) date_to_string(my_date) # Default format '2026-01-03' date_to_string(my_date, '%d/%m/%Y') # European format '03/01/2026' date_to_string(my_date, '%Y-%m-%d %H:%M:%S') # With time '2026-01-03 14:30:45'

Cost: O(1)

Source code in shortfx/fxDate/date_convertions.py
def date_to_string(date_input: datetime, format_code: str = '%Y-%m-%d') -> str:
    """Converts a datetime object into a formatted string.

    Description:
        Formats a datetime object as a string using Python's strftime format codes.
        Useful for displaying dates in human-readable formats or for system
        integration requiring specific date string conventions.

    Args:
        date_input (datetime): The datetime object to convert to a string.
        format_code (str, optional): The format code string (e.g., '%Y-%m-%d',
                                    '%d/%m/%Y %H:%M:%S'). Defaults to '%Y-%m-%d'.

    Returns:
        str: The formatted date string.

    Raises:
        TypeError: If 'date_input' is not a datetime object.

    Usage Example:
        >>> from datetime import datetime
        >>> from shortfx.fxDate.date_convertions import date_to_string
        >>> my_date = datetime(2026, 1, 3, 14, 30, 45)
        >>> date_to_string(my_date)  # Default format
        '2026-01-03'
        >>> date_to_string(my_date, '%d/%m/%Y')  # European format
        '03/01/2026'
        >>> date_to_string(my_date, '%Y-%m-%d %H:%M:%S')  # With time
        '2026-01-03 14:30:45'

    Cost: O(1)
    """
    if not isinstance(date_input, datetime):
        raise TypeError("Input 'date_input' must be a datetime object.")

    # Use the strftime method to format the datetime object into a string.
    return date_input.strftime(format_code)

datetime_to_ad_format(p_dt_input: Union[datetime, date]) -> str

Convierte un objeto datetime o date a la cadena de formato 'YYYYMMDDHHMMSS.0Z' utilizada en Active Directory para atributos como 'whenCreated'.

Parameters:

Name Type Description Default
p_dt_input Union[datetime, date]

El objeto datetime o date a convertir. Si es un objeto date, se asumirá medianoche (00:00:00) UTC. Si es un objeto datetime naive, se asumirá que es UTC. Si es un objeto datetime timezone-aware, se convertirá a UTC.

required

Returns:

Name Type Description
str str

La fecha y hora formateada como 'YYYYMMDDHHMMSS.0Z'.

Raises:

Type Description
TypeError

Si la entrada no es un objeto datetime ni date.

Source code in shortfx/fxDate/date_convertions.py
def datetime_to_ad_format(p_dt_input: Union[datetime, date]) -> str:
    """
    Convierte un objeto datetime o date a la cadena de formato
    'YYYYMMDDHHMMSS.0Z' utilizada en Active Directory para atributos
    como 'whenCreated'.

    Args:
        p_dt_input (Union[datetime, date]): El objeto datetime o date a convertir.
                                             Si es un objeto `date`, se asumirá medianoche (00:00:00) UTC.
                                             Si es un objeto `datetime` naive, se asumirá que es UTC.
                                             Si es un objeto `datetime` timezone-aware, se convertirá a UTC.

    Returns:
        str: La fecha y hora formateada como 'YYYYMMDDHHMMSS.0Z'.

    Raises:
        TypeError: Si la entrada no es un objeto datetime ni date.
    """
    if not isinstance(p_dt_input, (datetime, date)):
        raise TypeError("La entrada debe ser un objeto 'datetime' o 'date'.")

    dt_utc: datetime

    # Si es un objeto date, lo convertimos a datetime a medianoche UTC
    if isinstance(p_dt_input, date) and not isinstance(p_dt_input, datetime):
        dt_utc = datetime(p_dt_input.year, p_dt_input.month, p_dt_input.day, 0, 0, 0, 0, tzinfo=timezone.utc)
    # Si es un objeto datetime, lo convertimos a UTC si es necesario
    elif p_dt_input.tzinfo is None:
        # Si es naive (sin información de zona horaria), asumimos que es UTC.
        dt_utc = p_dt_input.replace(tzinfo=timezone.utc)
    else:
        # Si ya es consciente de la zona horaria, lo convertimos a UTC.
        dt_utc = p_dt_input.astimezone(timezone.utc)

    # Formatear el objeto datetime final al formato 'YYYYMMDDHHMMSS.0Z'
    # strftime para la parte numérica y concatenar ".0Z"
    return dt_utc.strftime("%Y%m%d%H%M%S") + ".0Z"

datetime_to_cron(dt_input: Union[datetime, date]) -> str

Convert a datetime to a cron schedule expression.

Produces a cron string matching the exact minute, hour, day, month. The day-of-week field is * (any).

Parameters:

Name Type Description Default
dt_input Union[datetime, date]

Datetime (or date, which defaults to midnight).

required

Returns:

Type Description
str

Cron expression string "minute hour day month *".

Example

from datetime import datetime datetime_to_cron(datetime(2025, 6, 15, 14, 30)) '30 14 15 6 *'

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def datetime_to_cron(dt_input: Union[datetime, date]) -> str:
    """Convert a datetime to a cron schedule expression.

    Produces a cron string matching the exact minute, hour, day, month.
    The day-of-week field is ``*`` (any).

    Args:
        dt_input: Datetime (or date, which defaults to midnight).

    Returns:
        Cron expression string ``"minute hour day month *"``.

    Example:
        >>> from datetime import datetime
        >>> datetime_to_cron(datetime(2025, 6, 15, 14, 30))
        '30 14 15 6 *'

    Complexity: O(1)
    """

    if isinstance(dt_input, date) and not isinstance(dt_input, datetime):
        dt_input = datetime(dt_input.year, dt_input.month, dt_input.day)

    return f"{dt_input.minute} {dt_input.hour} {dt_input.day} {dt_input.month} *"

datetime_to_date(datetime_obj: datetime) -> date

Converts a datetime object to a date object, discarding time components.

Description

Extracts only the date portion (year, month, day) from a datetime object, removing all time information (hour, minute, second, microsecond). Useful for date-only comparisons and storage.

Parameters:

Name Type Description Default
datetime_obj datetime

The datetime object to convert.

required

Returns:

Name Type Description
date date

A new date object representing the date part of the input datetime.

Raises:

Type Description
TypeError

If 'datetime_obj' is not a datetime object.

Usage Example

from datetime import datetime, date from shortfx.fxDate.date_convertions import datetime_to_date dt_with_time = datetime(2026, 1, 3, 14, 30, 45) date_only = datetime_to_date(dt_with_time) print(date_only) 2026-01-03 print(type(date_only))

Cost: O(1)

Source code in shortfx/fxDate/date_convertions.py
def datetime_to_date(datetime_obj: datetime) -> date:
    """Converts a datetime object to a date object, discarding time components.

    Description:
        Extracts only the date portion (year, month, day) from a datetime object,
        removing all time information (hour, minute, second, microsecond).
        Useful for date-only comparisons and storage.

    Args:
        datetime_obj (datetime): The datetime object to convert.

    Returns:
        date: A new date object representing the date part of the input datetime.

    Raises:
        TypeError: If 'datetime_obj' is not a datetime object.

    Usage Example:
        >>> from datetime import datetime, date
        >>> from shortfx.fxDate.date_convertions import datetime_to_date
        >>> dt_with_time = datetime(2026, 1, 3, 14, 30, 45)
        >>> date_only = datetime_to_date(dt_with_time)
        >>> print(date_only)
        2026-01-03
        >>> print(type(date_only))
        <class 'datetime.date'>

    Cost: O(1)
    """
    # Validate that the input is indeed a datetime object.
    # This ensures the '.date()' method can be safely called and
    # provides clear feedback for incorrect usage.
    if not isinstance(datetime_obj, datetime):
        raise TypeError("Input 'datetime_obj' must be a datetime object.")

    # The .date() method of a datetime object directly returns a date object
    # containing only the year, month, and day components.
    return datetime_obj.date()

datetime_to_filetime(p_dt: Union[datetime, date]) -> int

Convierte un objeto datetime o date de Python a un valor FILETIME de Windows.

FILETIME es el número de intervalos de 100 nanosegundos transcurridos desde el 1 de enero de 1601, 00:00:00 UTC.

Parameters:

Name Type Description Default
p_dt Union[datetime, date]

El objeto datetime o date a convertir. Si es un objeto date, se tratará como medianoche (00:00:00) UTC de ese día. Si es un objeto datetime naive, se asumirá que está en UTC. Si es un objeto datetime timezone-aware, se convertirá a UTC.

required

Returns:

Name Type Description
int int

El valor FILETIME como un entero de 64 bits.

Raises:

Type Description
TypeError

Si p_dt no es un objeto datetime ni date.

Source code in shortfx/fxDate/date_convertions.py
def datetime_to_filetime(p_dt: Union[datetime, date]) -> int:
    """
    Convierte un objeto datetime o date de Python a un valor FILETIME de Windows.

    FILETIME es el número de intervalos de 100 nanosegundos transcurridos
    desde el 1 de enero de 1601, 00:00:00 UTC.

    Args:
        p_dt (Union[datetime, date]): El objeto datetime o date a convertir.
                                      Si es un objeto date, se tratará como medianoche (00:00:00) UTC de ese día.
                                      Si es un objeto datetime naive, se asumirá que está en UTC.
                                      Si es un objeto datetime timezone-aware, se convertirá a UTC.

    Returns:
        int: El valor FILETIME como un entero de 64 bits.

    Raises:
        TypeError: Si p_dt no es un objeto datetime ni date.
    """
    if not isinstance(p_dt, (datetime, date)): # Aceptar tanto datetime como date
        raise TypeError("La entrada 'p_dt' debe ser un objeto datetime o date.")

    dt_utc: datetime

    # Si es un objeto date, convertirlo a datetime a medianoche UTC
    if isinstance(p_dt, date) and not isinstance(p_dt, datetime):
        dt_utc = datetime(p_dt.year, p_dt.month, p_dt.day, 0, 0, 0, 0, tzinfo=timezone.utc)
    # Si ya es un objeto datetime, manejar su timezone
    elif p_dt.tzinfo is None:
        # Si es naive, asumimos que ya representa una hora UTC.
        dt_utc = p_dt.replace(tzinfo=timezone.utc)
    else:
        # Si ya es consciente de la zona horaria, lo convertimos a UTC.
        dt_utc = p_dt.astimezone(timezone.utc)

    # Definir el epoch de FILETIME (1 de enero de 1601, 00:00:00 UTC)
    filetime_epoch = datetime(1601, 1, 1, 0, 0, 0, tzinfo=timezone.utc)

    # Calcular la diferencia de tiempo entre la fecha dada y el epoch de FILETIME
    delta = dt_utc - filetime_epoch

    # Convertir la diferencia total a segundos y luego a intervalos de 100 nanosegundos.
    # Cada segundo tiene 10,000,000 de intervalos de 100 nanosegundos.
    filetime_value = int(delta.total_seconds() * 10_000_000)

    return filetime_value

datetime_to_integer(date_input: Union[datetime, date, str], input_format: str = None) -> int

Converts a date to an integer in YYYYMMDD format, ignoring time.

Description

Transforms a date into an integer representation (YYYYMMDD) for compact storage and easy numerical comparisons. Time components are ignored. Accepts datetime, date objects, or strings.

Parameters:

Name Type Description Default
date_input Union[datetime, date, str]

The date to convert. Can be datetime, date, or string.

required
input_format str

Format code if 'date_input' is a string. Required when 'date_input' is a string.

None

Returns:

Name Type Description
int int

The date as YYYYMMDD integer (e.g., 20260103).

Raises:

Type Description
TypeError

If 'date_input' is not datetime, date, or string.

ValueError

If 'date_input' is string and 'input_format' not provided, or if parsing fails.

Usage Example

from datetime import datetime, date from shortfx.fxDate.date_convertions import datetime_to_integer datetime_to_integer(datetime(2026, 1, 3)) 20260103 datetime_to_integer(date(2026, 1, 3)) 20260103 datetime_to_integer("03/01/2026", "%d/%m/%Y") 20260103

Cost: O(1)

Source code in shortfx/fxDate/date_convertions.py
def datetime_to_integer(date_input: Union[datetime, date, str], input_format: str = None) -> int:
    """Converts a date to an integer in YYYYMMDD format, ignoring time.

    Description:
        Transforms a date into an integer representation (YYYYMMDD) for
        compact storage and easy numerical comparisons. Time components
        are ignored. Accepts datetime, date objects, or strings.

    Args:
        date_input (Union[datetime, date, str]): The date to convert.
                                                 Can be datetime, date, or string.
        input_format (str, optional): Format code if 'date_input' is a string.
                                     Required when 'date_input' is a string.

    Returns:
        int: The date as YYYYMMDD integer (e.g., 20260103).

    Raises:
        TypeError: If 'date_input' is not datetime, date, or string.
        ValueError: If 'date_input' is string and 'input_format' not provided,
                   or if parsing fails.

    Usage Example:
        >>> from datetime import datetime, date
        >>> from shortfx.fxDate.date_convertions import datetime_to_integer
        >>> datetime_to_integer(datetime(2026, 1, 3))
        20260103
        >>> datetime_to_integer(date(2026, 1, 3))
        20260103
        >>> datetime_to_integer("03/01/2026", "%d/%m/%Y")
        20260103

    Cost: O(1)
    """
    parsed_dt: Union[datetime, date]

    if isinstance(date_input, str):
        if input_format is None:
            raise ValueError("'input_format' is required when 'date_input' is a string.")
        try:
            parsed_dt = datetime.strptime(date_input, input_format)
        except ValueError as e:
            raise ValueError(f"Could not parse date string '{date_input}' with format '{input_format}'. Error: {e}") from e
    elif isinstance(date_input, (datetime, date)):
        parsed_dt = date_input
    else:
        raise TypeError("Input 'date_input' must be a datetime object, date object or a string.")

    # The conversion naturally only uses year, month, and day, ignoring time
    yyyymmdd_integer = parsed_dt.year * 10000 + parsed_dt.month * 100 + parsed_dt.day

    return yyyymmdd_integer

datetime_to_iso8601(dt: Union[date, datetime]) -> str

Converts a date or datetime to an ISO 8601 string.

Description

Returns the YYYY-MM-DD form for date objects and YYYY-MM-DDTHH:MM:SS (with optional timezone) for datetime objects.

Parameters:

Name Type Description Default
dt Union[date, datetime]

A date or datetime instance.

required

Returns:

Type Description
str

ISO 8601 formatted string.

Raises:

Type Description
TypeError

If dt is not a date or datetime.

Usage Example

from datetime import date datetime_to_iso8601(date(2024, 3, 15)) '2024-03-15'

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def datetime_to_iso8601(dt: Union[date, datetime]) -> str:
    """Converts a date or datetime to an ISO 8601 string.

    Description:
        Returns the ``YYYY-MM-DD`` form for ``date`` objects and
        ``YYYY-MM-DDTHH:MM:SS`` (with optional timezone) for
        ``datetime`` objects.

    Args:
        dt: A ``date`` or ``datetime`` instance.

    Returns:
        ISO 8601 formatted string.

    Raises:
        TypeError: If *dt* is not a ``date`` or ``datetime``.

    Usage Example:
        >>> from datetime import date
        >>> datetime_to_iso8601(date(2024, 3, 15))
        '2024-03-15'

    Complexity: O(1)
    """
    if not isinstance(dt, (date, datetime)):
        raise TypeError("dt must be a date or datetime instance")

    return dt.isoformat()

datetime_to_milliseconds_timestamp(date_input: datetime) -> int

Convierte un objeto datetime a un sello de tiempo Unix en milisegundos.

Problema/Necesidad del Usuario: Muchos sistemas modernos (APIs, bases de datos NoSQL, sistemas de logging de alto rendimiento) utilizan sellos de tiempo en milisegundos en lugar de segundos para una mayor granularidad. Es crucial poder convertir datetime a este formato.

Objetivos del Producto: Facilitar la integración con sistemas que requieren sellos de tiempo de milisegundos, mejorando la compatibilidad y la precisión en el registro de eventos.

Descripción: Esta función toma un objeto datetime y lo convierte en el número de milisegundos transcurridos desde la Época Unix (1 de enero de 1970, 00:00:00 UTC). Asegúrate de que tu objeto datetime tenga información de zona horaria (UTC preferiblemente) para evitar ambigüedades.

Parameters:

Name Type Description Default
date_input datetime

El objeto datetime a convertir. Preferiblemente con tzinfo=timezone.utc.

required

Returns:

Name Type Description
int int

El sello de tiempo Unix en milisegundos.

Raises:

Type Description
TypeError

Si 'date_input' no es un objeto datetime.

Example

from datetime import datetime, timezone dt_utc = datetime(2025, 6, 11, 1, 58, 11, 123456, tzinfo=timezone.utc) datetime_to_milliseconds_timestamp(dt_utc) 1749597491123

Si el datetime no tiene información de zona horaria, se asume UTC

dt_naive = datetime(2025, 6, 11, 1, 58, 11, 123456) datetime_to_milliseconds_timestamp(dt_naive) # Se comportará como si fuera UTC 1749597491123

Cost: O(1)

Source code in shortfx/fxDate/date_convertions.py
def datetime_to_milliseconds_timestamp(date_input: datetime) -> int:
    """Convierte un objeto datetime a un sello de tiempo Unix en milisegundos.

    Problema/Necesidad del Usuario: Muchos sistemas modernos (APIs, bases de datos NoSQL,
    sistemas de logging de alto rendimiento) utilizan sellos de tiempo en milisegundos
    en lugar de segundos para una mayor granularidad. Es crucial poder convertir
    datetime a este formato.

    Objetivos del Producto: Facilitar la integración con sistemas que requieren sellos
    de tiempo de milisegundos, mejorando la compatibilidad y la precisión en el registro de eventos.

    Descripción: Esta función toma un objeto `datetime` y lo convierte en el número de
    milisegundos transcurridos desde la Época Unix (1 de enero de 1970, 00:00:00 UTC).
    Asegúrate de que tu objeto `datetime` tenga información de zona horaria (UTC preferiblemente)
    para evitar ambigüedades.

    Args:
        date_input (datetime): El objeto `datetime` a convertir. Preferiblemente con `tzinfo=timezone.utc`.

    Returns:
        int: El sello de tiempo Unix en milisegundos.

    Raises:
        TypeError: Si 'date_input' no es un objeto `datetime`.

    Example:
        >>> from datetime import datetime, timezone
        >>> dt_utc = datetime(2025, 6, 11, 1, 58, 11, 123456, tzinfo=timezone.utc)
        >>> datetime_to_milliseconds_timestamp(dt_utc)
        1749597491123
        >>> # Si el datetime no tiene información de zona horaria, se asume UTC
        >>> dt_naive = datetime(2025, 6, 11, 1, 58, 11, 123456)
        >>> datetime_to_milliseconds_timestamp(dt_naive) # Se comportará como si fuera UTC
        1749597491123

    Cost: O(1)
    """
    if not isinstance(date_input, datetime):
        raise TypeError("Input 'date_input' must be a datetime object.")

    # Aseguramos que el datetime es consciente de la zona horaria y en UTC para el timestamp Unix
    # Si date_input no tiene tzinfo, se asume UTC por defecto para el timestamp() de Python.
    # Pero para claridad y robustez, es buena práctica hacerla explícitamente UTC.
    if date_input.tzinfo is None:
        # Consideramos naive datetime como UTC para la conversión a timestamp Unix
        utc_dt = date_input.replace(tzinfo=timezone.utc)
    else:
        # Convertimos a UTC si ya es consciente de la zona horaria pero no es UTC
        utc_dt = date_input.astimezone(timezone.utc)

    # Convertir a segundos desde la época Unix y luego a milisegundos,
    # incluyendo los microsegundos del datetime.
    # El método timestamp() devuelve un float en segundos. Multiplicamos por 1000
    # y redondeamos a entero.
    return int(utc_dt.timestamp() * 1000)

datetime_to_rfc2822(dt_input: Union[datetime, date]) -> str

Format a datetime as an RFC 2822 string (email header format).

Parameters:

Name Type Description Default
dt_input Union[datetime, date]

Datetime or date to format.

required

Returns:

Type Description
str

RFC 2822 formatted string (e.g. "Sun, 15 Jun 2025 14:30:00 +0000").

Example

from datetime import datetime datetime_to_rfc2822(datetime(2025, 6, 15, 14, 30, 0)) 'Sun, 15 Jun 2025 14:30:00 +0000'

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def datetime_to_rfc2822(dt_input: Union[datetime, date]) -> str:
    """Format a datetime as an RFC 2822 string (email header format).

    Args:
        dt_input: Datetime or date to format.

    Returns:
        RFC 2822 formatted string (e.g. ``"Sun, 15 Jun 2025 14:30:00 +0000"``).

    Example:
        >>> from datetime import datetime
        >>> datetime_to_rfc2822(datetime(2025, 6, 15, 14, 30, 0))
        'Sun, 15 Jun 2025 14:30:00 +0000'

    Complexity: O(1)
    """
    from email.utils import format_datetime as _fmt

    if isinstance(dt_input, date) and not isinstance(dt_input, datetime):
        dt_input = datetime(dt_input.year, dt_input.month, dt_input.day,
                            tzinfo=timezone.utc)

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

    return _fmt(dt_input)

datetime_to_timestamp(date_input: datetime) -> float

Converts a datetime object to a Unix timestamp.

Description

Converts a datetime to Unix timestamp (seconds since epoch: January 1, 1970, 00:00:00 UTC). Best used with timezone-aware datetime objects in UTC. Naive datetimes are treated as local time.

Parameters:

Name Type Description Default
date_input datetime

The datetime object to convert. Recommended to be timezone-aware (preferably UTC) for accuracy.

required

Returns:

Name Type Description
float float

The Unix timestamp as a float (seconds since epoch).

Raises:

Type Description
TypeError

If 'date_input' is not a datetime object.

Usage Example

from datetime import datetime, timezone from shortfx.fxDate.date_convertions import datetime_to_timestamp dt_utc = datetime(2026, 1, 3, 0, 0, 0, tzinfo=timezone.utc) datetime_to_timestamp(dt_utc) 1735862400.0

Cost: O(1)

Source code in shortfx/fxDate/date_convertions.py
def datetime_to_timestamp(date_input: datetime) -> float:
    """Converts a datetime object to a Unix timestamp.

    Description:
        Converts a datetime to Unix timestamp (seconds since epoch:
        January 1, 1970, 00:00:00 UTC). Best used with timezone-aware
        datetime objects in UTC. Naive datetimes are treated as local time.

    Args:
        date_input (datetime): The datetime object to convert. Recommended to be
                              timezone-aware (preferably UTC) for accuracy.

    Returns:
        float: The Unix timestamp as a float (seconds since epoch).

    Raises:
        TypeError: If 'date_input' is not a datetime object.

    Usage Example:
        >>> from datetime import datetime, timezone
        >>> from shortfx.fxDate.date_convertions import datetime_to_timestamp
        >>> dt_utc = datetime(2026, 1, 3, 0, 0, 0, tzinfo=timezone.utc)
        >>> datetime_to_timestamp(dt_utc)
        1735862400.0

    Cost: O(1)
    """
    if not isinstance(date_input, datetime):
        raise TypeError("Input 'date_input' must be a datetime object.")

    # The .timestamp() method on a datetime object returns the Unix timestamp.
    # It handles timezone-aware objects correctly by converting to UTC first.
    # For naive objects, it assumes local time.
    return date_input.timestamp()

decimal_hours_to_time(hours: float) -> time

Converts decimal hours to a time object.

Parameters:

Name Type Description Default
hours float

Decimal hours (e.g. 1.5).

required

Returns:

Type Description
time

A datetime.time instance.

Raises:

Type Description
TypeError

If hours is not numeric.

ValueError

If hours is outside [0, 24).

Example

decimal_hours_to_time(1.5) datetime.time(1, 30)

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def decimal_hours_to_time(hours: float) -> time:
    """Converts decimal hours to a ``time`` object.

    Args:
        hours: Decimal hours (e.g. ``1.5``).

    Returns:
        A ``datetime.time`` instance.

    Raises:
        TypeError: If hours is not numeric.
        ValueError: If hours is outside [0, 24).

    Example:
        >>> decimal_hours_to_time(1.5)
        datetime.time(1, 30)

    Complexity: O(1)
    """
    if not isinstance(hours, (int, float)):
        raise TypeError("hours must be numeric")

    if hours < 0 or hours >= 24:
        raise ValueError("hours must be in [0, 24)")

    total_seconds = int(round(hours * 3600))
    h = total_seconds // 3600
    m = (total_seconds % 3600) // 60
    s = total_seconds % 60

    return time(h, m, s)

excel_serial_to_date(serial: Union[int, float]) -> date

Converts an Excel serial date number to a Python date.

Description

Excel uses a serial number system where 1 = 1900-01-01. This function accounts for the Lotus 1-2-3 leap year bug where serial 60 = 1900-02-29 (a non-existent date) — values above 60 are shifted by one day.

Parameters:

Name Type Description Default
serial Union[int, float]

Excel serial date number (≥ 1).

required

Returns:

Type Description
date

Corresponding date object.

Raises:

Type Description
TypeError

If serial is not numeric.

ValueError

If serial < 1 or equals 60 (the bug date).

Usage Example

excel_serial_to_date(44927) datetime.date(2023, 1, 1)

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def excel_serial_to_date(serial: Union[int, float]) -> date:
    """Converts an Excel serial date number to a Python date.

    Description:
        Excel uses a serial number system where 1 = 1900-01-01.
        This function accounts for the Lotus 1-2-3 leap year bug
        where serial 60 = 1900-02-29 (a non-existent date) — values
        above 60 are shifted by one day.

    Args:
        serial: Excel serial date number (≥ 1).

    Returns:
        Corresponding ``date`` object.

    Raises:
        TypeError: If *serial* is not numeric.
        ValueError: If *serial* < 1 or equals 60 (the bug date).

    Usage Example:
        >>> excel_serial_to_date(44927)
        datetime.date(2023, 1, 1)

    Complexity: O(1)
    """
    if not isinstance(serial, (int, float)):
        raise TypeError("serial must be numeric")

    serial_int = int(serial)

    if serial_int < 1:
        raise ValueError("serial must be >= 1")

    if serial_int == 60:
        raise ValueError("Serial 60 corresponds to the non-existent 1900-02-29 (Lotus bug)")

    # Excel epoch: serial 1 = 1900-01-01
    epoch = date(1899, 12, 31)

    if serial_int > 60:
        # Adjust for the Lotus 1-2-3 bug
        serial_int -= 1

    return epoch + timedelta(days=serial_int)

filetime_to_datetime(filetime: int) -> Tuple[datetime, str]

Converts a Windows FILETIME value to a datetime object and its string representation.

Problem/User Need: Windows systems store timestamps in FILETIME format (100-nanosecond intervals since January 1, 1601 UTC). These need to be converted to standard datetime objects for processing.

Product Goals: Provide accurate conversion of Windows FILETIME values while handling both pre-Unix epoch (before 1970) and modern dates correctly.

Description: This function converts a 64-bit integer representing Windows FILETIME (number of 100-nanosecond intervals since January 1, 1601 UTC) to: 1. A timezone-aware datetime object 2. Its ISO format string representation

Parameters:

Name Type Description Default
filetime int

The Windows FILETIME value to convert. Must be a positive integer.

required

Returns:

Type Description
Tuple[datetime, str]

Tuple[datetime, str]: A tuple containing: - datetime: The converted datetime object (UTC) - str: The ISO formatted string representation

Raises:

Type Description
TypeError

If filetime is not an integer

ValueError

If filetime is negative

Example
Modern date (post-1970)

dt, dt_str = filetime_to_datetime(132723834123456789) print(dt) 2022-01-15 12:30:12.345678+00:00 print(dt_str) 2022-01-15 12:30:12.345678

Historical date (pre-1970)

dt, dt_str = filetime_to_datetime(130611456000000000) print(dt) 1601-01-01 00:00:00+00:00 print(dt_str) 1601-01-01 00:00:00.000000

Source code in shortfx/fxDate/date_convertions.py
def filetime_to_datetime(filetime: int) -> Tuple[datetime, str]:
    """Converts a Windows FILETIME value to a datetime object and its string representation.

    Problem/User Need: Windows systems store timestamps in FILETIME format 
    (100-nanosecond intervals since January 1, 1601 UTC). These need to be 
    converted to standard datetime objects for processing.

    Product Goals: Provide accurate conversion of Windows FILETIME values while 
    handling both pre-Unix epoch (before 1970) and modern dates correctly.

    Description: This function converts a 64-bit integer representing Windows FILETIME 
    (number of 100-nanosecond intervals since January 1, 1601 UTC) to:
    1. A timezone-aware datetime object
    2. Its ISO format string representation

    Args:
        filetime (int): The Windows FILETIME value to convert.
                       Must be a positive integer.

    Returns:
        Tuple[datetime, str]: A tuple containing:
            - datetime: The converted datetime object (UTC)
            - str: The ISO formatted string representation

    Raises:
        TypeError: If filetime is not an integer
        ValueError: If filetime is negative

    Example:
        >>> # Modern date (post-1970)
        >>> dt, dt_str = filetime_to_datetime(132723834123456789)
        >>> print(dt)
        2022-01-15 12:30:12.345678+00:00
        >>> print(dt_str)
        2022-01-15 12:30:12.345678

        >>> # Historical date (pre-1970)
        >>> dt, dt_str = filetime_to_datetime(130611456000000000)
        >>> print(dt)
        1601-01-01 00:00:00+00:00
        >>> print(dt_str)
        1601-01-01 00:00:00.000000
    """
    # Input validation
    if not isinstance(filetime, int):
        raise TypeError("FILETIME must be an integer")
    if filetime < 0:
        raise ValueError("FILETIME cannot be negative")

    # Constants
    FILETIME_EPOCH_DIFFERENCE = 116444736000000000  # Difference between Windows and Unix epochs in 100ns intervals

    # Convert based on whether the date is before or after Unix epoch
    if filetime < FILETIME_EPOCH_DIFFERENCE:
        # Pre-1970 dates: Calculate directly from Windows epoch (1601-01-01)
        seconds = filetime / 10_000_000  # Convert 100ns intervals to seconds
        dt = datetime(1601, 1, 1, tzinfo=timezone.utc) + timedelta(seconds=seconds)
    else:
        # Post-1970 dates: Calculate from Unix epoch
        unix_epoch_100ns = filetime - FILETIME_EPOCH_DIFFERENCE
        seconds_since_unix_epoch = unix_epoch_100ns / 10_000_000
        dt = datetime(1970, 1, 1, tzinfo=timezone.utc) + timedelta(seconds=seconds_since_unix_epoch)

    # Return both the datetime object and its string representation
    return dt, dt.isoformat(sep=' ', timespec='microseconds')

from_iso_to_local_datetime(iso_string: str) -> datetime

Converts an ISO 8601 formatted string to a datetime object in the local timezone.

This function is designed to handle ISO 8601 strings that may or may not contain timezone information (e.g., 'Z' for UTC or a '+HH:MM' offset). It will parse the string and then convert the resulting datetime object to the system's local timezone.

Parameters:

Name Type Description Default
iso_string str

The date and time as an ISO 8601 formatted string. Examples: - '2023-10-26T15:30:45.123456' (naive, assumed local or UTC) - '2023-10-26T15:30:45Z' (UTC) - '2023-10-26T15:30:45+02:00' (UTC+2 offset)

required

Returns:

Name Type Description
datetime datetime

A timezone-aware datetime object representing the input date and time, adjusted to the system's local timezone.

Raises:

Type Description
TypeError

If iso_string is not a string.

ValueError

If iso_string is not a valid ISO 8601 format that datetime.fromisoformat() can parse, or if the local timezone cannot be determined.

Example

from datetime import datetime, timezone, timedelta

Example 1: ISO string with UTC 'Z' (Zulu time)

iso_utc_string = "2023-10-26T15:30:00Z" local_dt = from_iso_to_local_datetime(iso_utc_string) print(f"UTC string '{iso_utc_string}' -> Local: {local_dt}")

On a system in CEST (UTC+2), this would show: 2023-10-26 17:30:00+02:00
Example 2: ISO string with an explicit offset (+02:00)

iso_offset_string = "2023-10-26T15:30:00+02:00" local_dt = from_iso_to_local_datetime(iso_offset_string) print(f"Offset string '{iso_offset_string}' -> Local: {local_dt}")

On a system in CEST (UTC+2), this would show: 2023-10-26 15:30:00+02:00
Example 3: ISO string without timezone info (naive)
datetime.fromisoformat() treats naive strings as local by default,
but it's safer to make it aware first for consistent conversion.

iso_naive_string = "2023-10-26T15:30:00" local_dt = from_iso_to_local_datetime(iso_naive_string) print(f"Naive string '{iso_naive_string}' -> Local: {local_dt}")

On a system in CEST (UTC+2), this would show: 2023-10-26 15:30:00+02:00

Cost: O(1)

Source code in shortfx/fxDate/date_convertions.py
def from_iso_to_local_datetime(iso_string: str) -> datetime:
    """Converts an ISO 8601 formatted string to a datetime object in the local timezone.

    This function is designed to handle ISO 8601 strings that may or may not
    contain timezone information (e.g., 'Z' for UTC or a '+HH:MM' offset).
    It will parse the string and then convert the resulting datetime object
    to the system's local timezone.

    Args:
        iso_string (str): The date and time as an ISO 8601 formatted string.
                          Examples:
                          - '2023-10-26T15:30:45.123456' (naive, assumed local or UTC)
                          - '2023-10-26T15:30:45Z' (UTC)
                          - '2023-10-26T15:30:45+02:00' (UTC+2 offset)

    Returns:
        datetime: A timezone-aware datetime object representing the input date
                  and time, adjusted to the system's local timezone.

    Raises:
        TypeError: If `iso_string` is not a string.
        ValueError: If `iso_string` is not a valid ISO 8601 format that
                    `datetime.fromisoformat()` can parse, or if the local
                    timezone cannot be determined.

    Example:
        >>> from datetime import datetime, timezone, timedelta

        >>> # Example 1: ISO string with UTC 'Z' (Zulu time)
        >>> iso_utc_string = "2023-10-26T15:30:00Z"
        >>> local_dt = from_iso_to_local_datetime(iso_utc_string)
        >>> print(f"UTC string '{iso_utc_string}' -> Local: {local_dt}")
        # On a system in CEST (UTC+2), this would show: 2023-10-26 17:30:00+02:00

        >>> # Example 2: ISO string with an explicit offset (+02:00)
        >>> iso_offset_string = "2023-10-26T15:30:00+02:00"
        >>> local_dt = from_iso_to_local_datetime(iso_offset_string)
        >>> print(f"Offset string '{iso_offset_string}' -> Local: {local_dt}")
        # On a system in CEST (UTC+2), this would show: 2023-10-26 15:30:00+02:00

        >>> # Example 3: ISO string without timezone info (naive)
        >>> # datetime.fromisoformat() treats naive strings as local by default,
        # but it's safer to make it aware first for consistent conversion.
        >>> iso_naive_string = "2023-10-26T15:30:00"
        >>> local_dt = from_iso_to_local_datetime(iso_naive_string)
        >>> print(f"Naive string '{iso_naive_string}' -> Local: {local_dt}")
        # On a system in CEST (UTC+2), this would show: 2023-10-26 15:30:00+02:00

    Cost: O(1)
    """
    if not isinstance(iso_string, str):
        raise TypeError("Input 'iso_string' must be a string.")

    # 1. Parse the ISO string into a datetime object
    try:
        # Python 3.10's datetime.fromisoformat() doesn't support 'Z' suffix for UTC
        # Replace 'Z' with '+00:00' for compatibility
        normalized_string = iso_string.replace('Z', '+00:00') if iso_string.endswith('Z') else iso_string

        # datetime.fromisoformat() handles various ISO 8601 formats,
        # including those with explicit offsets like '+02:00'.
        dt_object = datetime.fromisoformat(normalized_string)
    except ValueError as e:
        raise ValueError(f"Invalid ISO 8601 string format: '{iso_string}'. Error: {e}") from e

    # 2. Handle timezone conversion using datetime.astimezone() which automatically
    #    determines the local timezone without needing explicit ZoneInfo("localtime")
    if dt_object.tzinfo is None:
        # If the parsed datetime object is naive (no timezone info),
        # assume it's in UTC, then convert it to local timezone.
        # This is a common and safe assumption for naive ISO strings from APIs.
        dt_object = dt_object.replace(tzinfo=timezone.utc)

    # Convert to local timezone using astimezone() without argument
    # This automatically uses the system's local timezone and works on all platforms
    return dt_object.astimezone()

hms_to_seconds(hms: str) -> int

Parses an H:MM:SS or M:SS string to total seconds.

Parameters:

Name Type Description Default
hms str

Duration string (e.g. '1:01:01' or '5:30').

required

Returns:

Type Description
int

Total seconds as an integer.

Raises:

Type Description
TypeError

If hms is not a string.

ValueError

If the format cannot be parsed.

Example

hms_to_seconds("1:01:01") 3661 hms_to_seconds("5:30") 330

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def hms_to_seconds(hms: str) -> int:
    """Parses an ``H:MM:SS`` or ``M:SS`` string to total seconds.

    Args:
        hms: Duration string (e.g. ``'1:01:01'`` or ``'5:30'``).

    Returns:
        Total seconds as an integer.

    Raises:
        TypeError: If hms is not a string.
        ValueError: If the format cannot be parsed.

    Example:
        >>> hms_to_seconds("1:01:01")
        3661
        >>> hms_to_seconds("5:30")
        330

    Complexity: O(1)
    """
    if not isinstance(hms, str):
        raise TypeError("hms must be a string")

    parts = hms.strip().split(":")

    if len(parts) == 3:
        return int(parts[0]) * 3600 + int(parts[1]) * 60 + int(parts[2])

    if len(parts) == 2:
        return int(parts[0]) * 60 + int(parts[1])

    raise ValueError(f"Cannot parse '{hms}' — expected H:MM:SS or M:SS")

integer_to_datetime(yyyymmdd: int) -> datetime

Converts a YYYYMMDD integer back to a datetime object.

Description

Inverse of datetime_to_integer. Parses an integer in YYYYMMDD format and returns the corresponding datetime at midnight.

Parameters:

Name Type Description Default
yyyymmdd int

Date encoded as integer (e.g. 20260103 → 2026-01-03).

required

Returns:

Name Type Description
datetime datetime

The parsed date at 00:00:00.

Raises:

Type Description
TypeError

If yyyymmdd is not an integer.

ValueError

If the integer does not represent a valid date.

Example

integer_to_datetime(20260103) datetime.datetime(2026, 1, 3, 0, 0) integer_to_datetime(20240229) datetime.datetime(2024, 2, 29, 0, 0)

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def integer_to_datetime(yyyymmdd: int) -> datetime:
    """Converts a YYYYMMDD integer back to a datetime object.

    Description:
        Inverse of ``datetime_to_integer``. Parses an integer in YYYYMMDD
        format and returns the corresponding datetime at midnight.

    Args:
        yyyymmdd: Date encoded as integer (e.g. 20260103 → 2026-01-03).

    Returns:
        datetime: The parsed date at 00:00:00.

    Raises:
        TypeError: If *yyyymmdd* is not an integer.
        ValueError: If the integer does not represent a valid date.

    Example:
        >>> integer_to_datetime(20260103)
        datetime.datetime(2026, 1, 3, 0, 0)
        >>> integer_to_datetime(20240229)
        datetime.datetime(2024, 2, 29, 0, 0)

    Complexity: O(1)
    """
    if not isinstance(yyyymmdd, int):
        raise TypeError("Input 'yyyymmdd' must be an integer.")

    year = yyyymmdd // 10000
    month = (yyyymmdd % 10000) // 100
    day = yyyymmdd % 100

    try:
        return datetime(year, month, day)
    except ValueError as exc:
        raise ValueError(
            f"Integer {yyyymmdd} does not represent a valid date "
            f"(year={year}, month={month}, day={day})."
        ) from exc

iso8601_to_datetime(text: str) -> Union[date, datetime]

Parses an ISO 8601 string to a date or datetime object.

Description

Supports YYYY-MM-DD (returns date) and YYYY-MM-DDTHH:MM:SS[±HH:MM] (returns datetime).

Parameters:

Name Type Description Default
text str

The ISO 8601 string.

required

Returns:

Type Description
Union[date, datetime]

A date or datetime object.

Raises:

Type Description
TypeError

If text is not a string.

ValueError

If text cannot be parsed.

Usage Example

iso8601_to_datetime("2024-03-15") datetime.date(2024, 3, 15)

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def iso8601_to_datetime(text: str) -> Union[date, datetime]:
    """Parses an ISO 8601 string to a date or datetime object.

    Description:
        Supports ``YYYY-MM-DD`` (returns ``date``) and
        ``YYYY-MM-DDTHH:MM:SS[±HH:MM]`` (returns ``datetime``).

    Args:
        text: The ISO 8601 string.

    Returns:
        A ``date`` or ``datetime`` object.

    Raises:
        TypeError: If *text* is not a string.
        ValueError: If *text* cannot be parsed.

    Usage Example:
        >>> iso8601_to_datetime("2024-03-15")
        datetime.date(2024, 3, 15)

    Complexity: O(1)
    """
    if not isinstance(text, str):
        raise TypeError("text must be a string")

    cleaned = text.strip()

    if "T" in cleaned or " " in cleaned:
        return datetime.fromisoformat(cleaned)

    return date.fromisoformat(cleaned)

iso_week_date_to_date(text: str) -> date

Parse an ISO 8601 week date string to a date object.

Description

Accepts strings in YYYY-Www-D format and returns the corresponding date.

Parameters:

Name Type Description Default
text str

The ISO week date string (e.g. '2023-W01-1').

required

Returns:

Type Description
date

A date object.

Raises:

Type Description
TypeError

If text is not a string.

ValueError

If text does not match the expected format or contains invalid week/day numbers.

Usage Example

iso_week_date_to_date("2023-W01-1") datetime.date(2023, 1, 2)

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def iso_week_date_to_date(text: str) -> date:
    """Parse an ISO 8601 week date string to a date object.

    Description:
        Accepts strings in ``YYYY-Www-D`` format and returns the
        corresponding ``date``.

    Args:
        text: The ISO week date string (e.g. ``'2023-W01-1'``).

    Returns:
        A ``date`` object.

    Raises:
        TypeError: If *text* is not a string.
        ValueError: If *text* does not match the expected format or
            contains invalid week/day numbers.

    Usage Example:
        >>> iso_week_date_to_date("2023-W01-1")
        datetime.date(2023, 1, 2)

    Complexity: O(1)
    """
    if not isinstance(text, str):
        raise TypeError("text must be a string")

    m = re.match(r"^(\d{4})-W(\d{2})-(\d)$", text.strip())

    if not m:
        raise ValueError(f"Invalid ISO week date format: '{text}'")

    iso_year = int(m.group(1))
    iso_week = int(m.group(2))
    iso_day = int(m.group(3))

    if iso_week < 1 or iso_week > 53:
        raise ValueError(f"ISO week must be between 1 and 53, got {iso_week}")

    if iso_day < 1 or iso_day > 7:
        raise ValueError(f"ISO day must be between 1 and 7, got {iso_day}")

    # Jan 4th is always in ISO week 1
    jan4 = date(iso_year, 1, 4)
    # Monday of ISO week 1
    week1_monday = jan4 - timedelta(days=jan4.weekday())
    result = week1_monday + timedelta(weeks=iso_week - 1, days=iso_day - 1)

    # Verify the result maps back to the requested ISO year/week
    ry, rw, rd = result.isocalendar()

    if ry != iso_year or rw != iso_week:
        raise ValueError(
            f"Week {iso_week} does not exist in ISO year {iso_year}"
        )

    return result

julian_to_date(julian_date: int, year: int) -> datetime

Convierte una fecha Juliana (día del año) a un objeto datetime estándar.

Problema/Necesidad del Usuario: Cuando se reciben fechas en formato Julian (día del año) desde sistemas externos, es necesario convertirlas a un formato de fecha estándar para su manipulación y visualización.

Objetivos del Producto: Proporcionar una forma directa de construir una fecha estándar a partir de su número de día del año, manejando correctamente los años bisiestos.

Descripción: Dada un número de día del año (fecha Juliana, de 1 a 366) y un año, esta función reconstruye y devuelve el objeto datetime correspondiente. La fecha devuelta tendrá su componente de tiempo establecido a medianoche (00:00:00).

Parameters:

Name Type Description Default
julian_date int

El número del día del año (1 a 365 o 366). Debe ser un entero positivo.

required
year int

El año al que pertenece la fecha Juliana.

required

Returns:

Name Type Description
datetime datetime

Un objeto datetime representando la fecha calculada (a medianoche).

Raises:

Type Description
TypeError

Si 'julian_date' o 'year' no son enteros.

ValueError

Si 'year' está fuera del rango válido (1-9999), o si 'julian_date' está fuera del rango válido (1 a 365/366 dependiendo del año bisiesto).

Example

from datetime import datetime

Ejemplo 1: Día 1 del año 2025

julian_to_date(1, 2025) datetime.datetime(2025, 1, 1, 0, 0)

Ejemplo 2: Día 32 del año 2025 (Febrero 1)

julian_to_date(32, 2025) datetime.datetime(2025, 2, 1, 0, 0)

Ejemplo 3: Día 61 del año 2024 (Marzo 1, año bisiesto)

julian_to_date(61, 2024) datetime.datetime(2024, 3, 1, 0, 0) # 31 (Ene) + 29 (Feb 2024) + 1 (Mar) = 61

Ejemplo 4: Día 365 del año 2023 (Diciembre 31, año no bisiesto)

julian_to_date(365, 2023) datetime.datetime(2023, 12, 31, 0, 0)

Ejemplo 5: Día 366 del año 2024 (Diciembre 31, año bisiesto)

julian_to_date(366, 2024) datetime.datetime(2024, 12, 31, 0, 0)

Día Juliana inválido para un año (levantará ValueError)

try: julian_to_date(366, 2023) # 2023 no es bisiesto, solo tiene 365 días except ValueError as e: print(f"Error: {e}")

Expected output: Error: Julian date must be between 1 and 365 for year 2023.

Cost: O(1)

Source code in shortfx/fxDate/date_convertions.py
def julian_to_date(julian_date: int, year: int) -> datetime:
    """Convierte una fecha Juliana (día del año) a un objeto datetime estándar.

    Problema/Necesidad del Usuario: Cuando se reciben fechas en formato Julian (día del año)
    desde sistemas externos, es necesario convertirlas a un formato de fecha estándar
    para su manipulación y visualización.

    Objetivos del Producto: Proporcionar una forma directa de construir una fecha
    estándar a partir de su número de día del año, manejando correctamente los años bisiestos.

    Descripción: Dada un número de día del año (fecha Juliana, de 1 a 366) y un año,
    esta función reconstruye y devuelve el objeto `datetime` correspondiente.
    La fecha devuelta tendrá su componente de tiempo establecido a medianoche (00:00:00).

    Args:
        julian_date (int): El número del día del año (1 a 365 o 366).
                           Debe ser un entero positivo.
        year (int): El año al que pertenece la fecha Juliana.

    Returns:
        datetime: Un objeto `datetime` representando la fecha calculada (a medianoche).

    Raises:
        TypeError: Si 'julian_date' o 'year' no son enteros.
        ValueError: Si 'year' está fuera del rango válido (1-9999),
                    o si 'julian_date' está fuera del rango válido (1 a 365/366
                    dependiendo del año bisiesto).

    Example:
        >>> from datetime import datetime

        >>> # Ejemplo 1: Día 1 del año 2025
        >>> julian_to_date(1, 2025)
        datetime.datetime(2025, 1, 1, 0, 0)

        >>> # Ejemplo 2: Día 32 del año 2025 (Febrero 1)
        >>> julian_to_date(32, 2025)
        datetime.datetime(2025, 2, 1, 0, 0)

        >>> # Ejemplo 3: Día 61 del año 2024 (Marzo 1, año bisiesto)
        >>> julian_to_date(61, 2024)
        datetime.datetime(2024, 3, 1, 0, 0) # 31 (Ene) + 29 (Feb 2024) + 1 (Mar) = 61

        >>> # Ejemplo 4: Día 365 del año 2023 (Diciembre 31, año no bisiesto)
        >>> julian_to_date(365, 2023)
        datetime.datetime(2023, 12, 31, 0, 0)

        >>> # Ejemplo 5: Día 366 del año 2024 (Diciembre 31, año bisiesto)
        >>> julian_to_date(366, 2024)
        datetime.datetime(2024, 12, 31, 0, 0)

        >>> # Día Juliana inválido para un año (levantará ValueError)
        >>> try:
        >>>     julian_to_date(366, 2023) # 2023 no es bisiesto, solo tiene 365 días
        >>> except ValueError as e:
        >>>     print(f"Error: {e}")
        # Expected output: Error: Julian date must be between 1 and 365 for year 2023.

    Cost: O(1)
    """
    # 1. Validación de entradas
    if not isinstance(julian_date, int):
        raise TypeError("Input 'julian_date' must be an integer.")
    if not isinstance(year, int):
        raise TypeError("Input 'year' must be an integer.")

    if not (1 <= year <= 9999): # Rango común de años para objetos datetime
        raise ValueError("Year is out of valid range (1-9999).")

    # Determinar el número máximo de días en el año para validar julian_date
    max_days_in_year = 366 if calendar.isleap(year) else 365
    if not (1 <= julian_date <= max_days_in_year):
        raise ValueError(f"Julian date must be between 1 and {max_days_in_year} for year {year}.")

    # Crear una fecha para el 1 de enero del año dado
    start_of_year = datetime(year, 1, 1)

    # Añadir (julian_date - 1) días a la fecha del 1 de enero para obtener la fecha deseada.
    # Se resta 1 porque julian_date es 1-indexed (día 1 es Enero 1).
    target_date = start_of_year + timedelta(days=julian_date - 1)

    # Devolver la fecha con la hora a medianoche para consistencia
    return target_date.replace(hour=0, minute=0, second=0, microsecond=0)

list_available_timezones() -> List[str]

Retrieves and returns a sorted list of all available IANA time zone names.

These names are used by the 'zoneinfo' module (and other timezone libraries) to accurately handle time conversions, daylight saving rules, and historical offsets.

Returns:

Type Description
List[str]

List[str]: A sorted list of strings, where each string is a valid IANA time zone name.

Example

timezones = list_available_timezones()

The exact output depends on your system's timezone data, but will include many.

print(timezones[:5]) # Print the first 5 for brevity

Expected output might look like: ['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa', 'Africa/Algiers', 'Africa/Asmara']

print('Europe/Madrid' in timezones) True

Cost: O(n log n) where n is the number of available timezones

Source code in shortfx/fxDate/date_convertions.py
def list_available_timezones() -> List[str]:
    """Retrieves and returns a sorted list of all available IANA time zone names.

    These names are used by the 'zoneinfo' module (and other timezone libraries)
    to accurately handle time conversions, daylight saving rules, and historical offsets.

    Returns:
        List[str]: A sorted list of strings, where each string is a valid IANA time zone name.

    Example:
        >>> timezones = list_available_timezones()
        >>> # The exact output depends on your system's timezone data, but will include many.
        >>> print(timezones[:5]) # Print the first 5 for brevity
        # Expected output might look like: ['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa', 'Africa/Algiers', 'Africa/Asmara']
        >>> print('Europe/Madrid' in timezones)
        True

    Cost: O(n log n) where n is the number of available timezones
    """
    # zoneinfo.available_timezones() returns a set of all available timezone keys.
    # We convert it to a list and sort it for consistent, readable output.
    return sorted(list(zoneinfo.available_timezones()))

milliseconds_timestamp_to_datetime(timestamp_ms: int) -> datetime

Convierte un sello de tiempo Unix en milisegundos a un objeto datetime.

Problema/Necesidad del Usuario: La contraparte de la anterior. Poder tomar un sello de tiempo en milisegundos y convertirlo de nuevo a un objeto datetime para su manipulación y visualización.

Objetivos del Producto: Completar el ciclo de conversión con sellos de tiempo de milisegundos, permitiendo el procesamiento bidireccional de datos de alta resolución.

Descripción: Esta función toma un entero que representa un sello de tiempo Unix en milisegundos y lo convierte en un objeto datetime consciente de la zona horaria, establecido en UTC.

Parameters:

Name Type Description Default
timestamp_ms int

El sello de tiempo Unix en milisegundos a convertir.

required

Returns:

Name Type Description
datetime datetime

Un objeto datetime que representa la fecha y hora del sello de tiempo, con información de zona horaria UTC.

Raises:

Type Description
TypeError

Si 'timestamp_ms' no es un entero.

ValueError

Si el sello de tiempo es negativo, lo que no es válido en el contexto Unix.

Example

from datetime import datetime, timezone milliseconds_timestamp_to_datetime(1749597491123) datetime.datetime(2025, 6, 11, 1, 58, 11, 123000, tzinfo=datetime.timezone.utc)

Cost: O(1)

Source code in shortfx/fxDate/date_convertions.py
def milliseconds_timestamp_to_datetime(timestamp_ms: int) -> datetime:
    """Convierte un sello de tiempo Unix en milisegundos a un objeto datetime.

    Problema/Necesidad del Usuario: La contraparte de la anterior. Poder tomar
    un sello de tiempo en milisegundos y convertirlo de nuevo a un objeto datetime
    para su manipulación y visualización.

    Objetivos del Producto: Completar el ciclo de conversión con sellos de tiempo
    de milisegundos, permitiendo el procesamiento bidireccional de datos de alta resolución.

    Descripción: Esta función toma un entero que representa un sello de tiempo Unix
    en milisegundos y lo convierte en un objeto `datetime` consciente de la zona horaria,
    establecido en UTC.

    Args:
        timestamp_ms (int): El sello de tiempo Unix en milisegundos a convertir.

    Returns:
        datetime: Un objeto `datetime` que representa la fecha y hora del sello de tiempo,
                  con información de zona horaria UTC.

    Raises:
        TypeError: Si 'timestamp_ms' no es un entero.
        ValueError: Si el sello de tiempo es negativo, lo que no es válido en el contexto Unix.

    Example:
        >>> from datetime import datetime, timezone
        >>> milliseconds_timestamp_to_datetime(1749597491123)
        datetime.datetime(2025, 6, 11, 1, 58, 11, 123000, tzinfo=datetime.timezone.utc)

    Cost: O(1)
    """
    if not isinstance(timestamp_ms, int):
        raise TypeError("Input 'timestamp_ms' must be an integer.")
    if timestamp_ms < 0:
        raise ValueError("Timestamp in milliseconds cannot be negative.")

    # Convertir milisegundos a segundos (float)
    timestamp_s = timestamp_ms / 1000.0

    # Crear el objeto datetime a partir del timestamp en segundos, asegurando que sea UTC.
    # El método fromtimestamp() por defecto usa la zona horaria local.
    # Para obtener un datetime UTC, es mejor usar utcfromtimestamp() en versiones anteriores a Python 3.3,
    # o de forma más robusta, datetime.fromtimestamp(timestamp, tz=timezone.utc) en Python 3.3+.
    return datetime.fromtimestamp(timestamp_s, tz=timezone.utc)

modified_julian_date(d: Union[date, datetime]) -> float

Convert a date to Modified Julian Date (MJD).

MJD = JD − 2400000.5, widely used in satellite tracking and radio astronomy.

Parameters:

Name Type Description Default
d Union[date, datetime]

Date or datetime to convert.

required

Returns:

Type Description
float

Modified Julian Date as a float.

Raises:

Type Description
TypeError

If d is not a date or datetime.

Example

modified_julian_date(date(2000, 1, 1)) 51544.0

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def modified_julian_date(d: Union[date, datetime]) -> float:
    """Convert a date to Modified Julian Date (MJD).

    MJD = JD − 2400000.5, widely used in satellite tracking and
    radio astronomy.

    Args:
        d: Date or datetime to convert.

    Returns:
        Modified Julian Date as a float.

    Raises:
        TypeError: If *d* is not a date or datetime.

    Example:
        >>> modified_julian_date(date(2000, 1, 1))
        51544.0

    Complexity: O(1)
    """
    if not isinstance(d, (date, datetime)):
        raise TypeError("d must be a date or datetime")

    target = d if isinstance(d, date) and not isinstance(d, datetime) else (d.date() if isinstance(d, datetime) else d)

    # Julian day algorithm (Meeus)
    y = target.year
    m = target.month
    day = target.day

    if m <= 2:
        y -= 1
        m += 12

    a = y // 100
    b = 2 - a + a // 4

    jd = int(365.25 * (y + 4716)) + int(30.6001 * (m + 1)) + day + b - 1524.5

    return jd - 2400000.5

parse_date_flexible(text: str, dayfirst: bool = True) -> Optional[Union[date, datetime]]

Parses a date string by trying multiple common formats.

Parameters:

Name Type Description Default
text str

The date string to parse.

required
dayfirst bool

When ambiguous, treat the first number as the day.

True

Returns:

Type Description
Optional[Union[date, datetime]]

A date or datetime object, or None if no format matches.

Example

parse_date_flexible("15/01/2026") datetime.datetime(2026, 1, 15, 0, 0) parse_date_flexible("2026-04-04") datetime.datetime(2026, 4, 4, 0, 0)

Complexity: O(f) where f is the number of formats tried.

Source code in shortfx/fxDate/date_convertions.py
def parse_date_flexible(
    text: str,
    dayfirst: bool = True,
) -> Optional[Union[date, datetime]]:
    """Parses a date string by trying multiple common formats.

    Args:
        text: The date string to parse.
        dayfirst: When ambiguous, treat the first number as the day.

    Returns:
        A ``date`` or ``datetime`` object, or ``None`` if no format matches.

    Example:
        >>> parse_date_flexible("15/01/2026")
        datetime.datetime(2026, 1, 15, 0, 0)
        >>> parse_date_flexible("2026-04-04")
        datetime.datetime(2026, 4, 4, 0, 0)

    Complexity: O(f) where f is the number of formats tried.
    """
    if not isinstance(text, str) or not text.strip():
        return None

    text = text.strip()
    formats = list(_DATE_FORMATS)

    if not dayfirst:
        # prioritise month-first patterns
        formats = [f for f in formats if "%m" in f.split("%d")[0]] + \
                  [f for f in formats if "%m" not in f.split("%d")[0]]

    for fmt in formats:

        try:
            return datetime.strptime(text, fmt)
        except ValueError:
            continue

    return None

rata_die(d: Union[date, datetime]) -> int

Convert a date to Rata Die (absolute day number).

Day 1 in Rata Die = January 1, AD 1 (proleptic Gregorian).

Parameters:

Name Type Description Default
d Union[date, datetime]

Date or datetime to convert.

required

Returns:

Type Description
int

Rata Die integer.

Raises:

Type Description
TypeError

If d is not a date or datetime.

Example

rata_die(date(2000, 1, 1)) 730120

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def rata_die(d: Union[date, datetime]) -> int:
    """Convert a date to Rata Die (absolute day number).

    Day 1 in Rata Die = January 1, AD 1 (proleptic Gregorian).

    Args:
        d: Date or datetime to convert.

    Returns:
        Rata Die integer.

    Raises:
        TypeError: If *d* is not a date or datetime.

    Example:
        >>> rata_die(date(2000, 1, 1))
        730120

    Complexity: O(1)
    """
    if not isinstance(d, (date, datetime)):
        raise TypeError("d must be a date or datetime")

    target = d.date() if isinstance(d, datetime) else d
    # date.toordinal() returns proleptic Gregorian ordinal where Jan 1 AD 1 = 1
    return target.toordinal()

rfc2822_to_datetime(text: str) -> datetime

Parse an RFC 2822 date string to a datetime object.

Parameters:

Name Type Description Default
text str

RFC 2822 formatted date string.

required

Returns:

Type Description
datetime

Timezone-aware datetime.

Raises:

Type Description
ValueError

If the string cannot be parsed.

Example

rfc2822_to_datetime('Sun, 15 Jun 2025 14:30:00 +0000') datetime.datetime(2025, 6, 15, 14, 30, tzinfo=datetime.timezone.utc)

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def rfc2822_to_datetime(text: str) -> datetime:
    """Parse an RFC 2822 date string to a datetime object.

    Args:
        text: RFC 2822 formatted date string.

    Returns:
        Timezone-aware datetime.

    Raises:
        ValueError: If the string cannot be parsed.

    Example:
        >>> rfc2822_to_datetime('Sun, 15 Jun 2025 14:30:00 +0000')
        datetime.datetime(2025, 6, 15, 14, 30, tzinfo=datetime.timezone.utc)

    Complexity: O(1)
    """
    from email.utils import parsedate_to_datetime as _parse

    try:
        return _parse(text)
    except Exception as exc:
        raise ValueError(f"Cannot parse RFC 2822 date: '{text}'.") from exc

seconds_to_hms(seconds: int) -> str

Converts total seconds to an H:MM:SS string.

Parameters:

Name Type Description Default
seconds int

Non-negative integer of seconds.

required

Returns:

Type Description
str

Formatted string like '1:01:01'.

Raises:

Type Description
TypeError

If seconds is not an integer.

ValueError

If seconds is negative.

Example

seconds_to_hms(3661) '1:01:01'

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def seconds_to_hms(seconds: int) -> str:
    """Converts total seconds to an ``H:MM:SS`` string.

    Args:
        seconds: Non-negative integer of seconds.

    Returns:
        Formatted string like ``'1:01:01'``.

    Raises:
        TypeError: If seconds is not an integer.
        ValueError: If seconds is negative.

    Example:
        >>> seconds_to_hms(3661)
        '1:01:01'

    Complexity: O(1)
    """
    if not isinstance(seconds, int):
        raise TypeError("seconds must be an integer")

    if seconds < 0:
        raise ValueError("seconds must be non-negative")

    h = seconds // 3600
    m = (seconds % 3600) // 60
    s = seconds % 60

    return f"{h}:{m:02d}:{s:02d}"

time_to_day_fraction(time_text: str) -> float

Convert a time string to a day fraction (0.0 – 1.0).

Parses a time in "HH:MM:SS" or "HH:MM" format and returns the proportion of a 24-hour day it represents. Equivalent to the Excel TIMEVALUE function.

Parameters:

Name Type Description Default
time_text str

Time string in "HH:MM:SS" or "HH:MM" format.

required

Returns:

Type Description
float

Float between 0.0 (inclusive) and 1.0 (exclusive).

Raises:

Type Description
ValueError

If time_text cannot be parsed.

Example

time_to_day_fraction("12:00:00") 0.5 time_to_day_fraction("06:00") 0.25

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def time_to_day_fraction(time_text: str) -> float:
    """Convert a time string to a day fraction (0.0 – 1.0).

    Parses a time in ``"HH:MM:SS"`` or ``"HH:MM"`` format and returns the
    proportion of a 24-hour day it represents.  Equivalent to the Excel
    ``TIMEVALUE`` function.

    Args:
        time_text: Time string in ``"HH:MM:SS"`` or ``"HH:MM"`` format.

    Returns:
        Float between 0.0 (inclusive) and 1.0 (exclusive).

    Raises:
        ValueError: If *time_text* cannot be parsed.

    Example:
        >>> time_to_day_fraction("12:00:00")
        0.5
        >>> time_to_day_fraction("06:00")
        0.25

    Complexity: O(1)
    """
    parts = time_text.strip().split(":")

    if len(parts) == 2:
        h, m, s = int(parts[0]), int(parts[1]), 0
    elif len(parts) == 3:
        h, m, s = int(parts[0]), int(parts[1]), int(parts[2])
    else:
        raise ValueError(f"Cannot parse time string: '{time_text}'.")

    total_seconds = h * 3600 + m * 60 + s
    return total_seconds / 86400.0

time_to_decimal_hours(t: time) -> float

Converts a time object to decimal hours.

Parameters:

Name Type Description Default
t time

A datetime.time instance.

required

Returns:

Type Description
float

Hours as a float (e.g. 1.5 for 01:30).

Raises:

Type Description
TypeError

If t is not a time instance.

Example

from datetime import time time_to_decimal_hours(time(1, 30)) 1.5

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def time_to_decimal_hours(t: time) -> float:
    """Converts a ``time`` object to decimal hours.

    Args:
        t: A ``datetime.time`` instance.

    Returns:
        Hours as a float (e.g. ``1.5`` for ``01:30``).

    Raises:
        TypeError: If t is not a ``time`` instance.

    Example:
        >>> from datetime import time
        >>> time_to_decimal_hours(time(1, 30))
        1.5

    Complexity: O(1)
    """
    if not isinstance(t, time):
        raise TypeError("t must be a datetime.time instance")

    return t.hour + t.minute / 60.0 + t.second / 3600.0 + t.microsecond / 3_600_000_000.0

time_zone_offset(tz_name: str, dt_val: Union[datetime, None] = None) -> str

Returns the UTC offset string for a timezone at a given datetime.

Parameters:

Name Type Description Default
tz_name str

IANA timezone name (e.g. "Europe/Madrid").

required
dt_val Union[datetime, None]

Reference datetime (defaults to now in UTC).

None

Returns:

Type Description
str

UTC offset string like "+02:00" or "-05:00".

Raises:

Type Description
TypeError

If tz_name is not a string.

ValueError

If tz_name is not a valid timezone.

Example

time_zone_offset("Europe/Madrid", datetime(2026, 7, 1)) '+02:00'

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def time_zone_offset(
    tz_name: str,
    dt_val: Union[datetime, None] = None,
) -> str:
    """Returns the UTC offset string for a timezone at a given datetime.

    Args:
        tz_name: IANA timezone name (e.g. ``"Europe/Madrid"``).
        dt_val: Reference datetime (defaults to now in UTC).

    Returns:
        UTC offset string like ``"+02:00"`` or ``"-05:00"``.

    Raises:
        TypeError: If tz_name is not a string.
        ValueError: If tz_name is not a valid timezone.

    Example:
        >>> time_zone_offset("Europe/Madrid", datetime(2026, 7, 1))
        '+02:00'

    Complexity: O(1)
    """
    if not isinstance(tz_name, str):
        raise TypeError("tz_name must be a string")

    try:
        tz = zoneinfo.ZoneInfo(tz_name)
    except (KeyError, zoneinfo.ZoneInfoNotFoundError) as exc:
        raise ValueError(f"Invalid timezone: {tz_name}") from exc

    ref = dt_val or datetime.now(tz=zoneinfo.ZoneInfo("UTC"))

    if ref.tzinfo is None:
        ref = ref.replace(tzinfo=zoneinfo.ZoneInfo("UTC"))

    localized = ref.astimezone(tz)
    offset = localized.utcoffset()

    if offset is None:
        return "+00:00"

    total_seconds = int(offset.total_seconds())
    sign = "+" if total_seconds >= 0 else "-"
    total_seconds = abs(total_seconds)
    hours, remainder = divmod(total_seconds, 3600)
    minutes = remainder // 60
    return f"{sign}{hours:02d}:{minutes:02d}"

timestamp_to_datetime(timestamp_input: Union[int, float]) -> datetime

Converts a Unix timestamp back to a datetime object.

Description

Converts a Unix timestamp (seconds since epoch: January 1, 1970, 00:00:00 UTC) to a timezone-aware datetime object in UTC. Complements datetime_to_timestamp().

Parameters:

Name Type Description Default
timestamp_input Union[int, float]

The Unix timestamp to convert.

required

Returns:

Name Type Description
datetime datetime

A timezone-aware datetime object in UTC.

Raises:

Type Description
TypeError

If 'timestamp_input' is not an integer or float.

ValueError

If timestamp is outside valid range for datetime conversion.

Usage Example

from shortfx.fxDate.date_convertions import timestamp_to_datetime timestamp_to_datetime(1735862400.0) datetime.datetime(2026, 1, 3, 0, 0, tzinfo=datetime.timezone.utc) timestamp_to_datetime(1735862400) datetime.datetime(2026, 1, 3, 0, 0, tzinfo=datetime.timezone.utc)

Cost: O(1)

Source code in shortfx/fxDate/date_convertions.py
def timestamp_to_datetime(timestamp_input: Union[int, float]) -> datetime:
    """Converts a Unix timestamp back to a datetime object.

    Description:
        Converts a Unix timestamp (seconds since epoch: January 1, 1970, 00:00:00 UTC)
        to a timezone-aware datetime object in UTC. Complements datetime_to_timestamp().

    Args:
        timestamp_input (Union[int, float]): The Unix timestamp to convert.

    Returns:
        datetime: A timezone-aware datetime object in UTC.

    Raises:
        TypeError: If 'timestamp_input' is not an integer or float.
        ValueError: If timestamp is outside valid range for datetime conversion.

    Usage Example:
        >>> from shortfx.fxDate.date_convertions import timestamp_to_datetime
        >>> timestamp_to_datetime(1735862400.0)
        datetime.datetime(2026, 1, 3, 0, 0, tzinfo=datetime.timezone.utc)
        >>> timestamp_to_datetime(1735862400)
        datetime.datetime(2026, 1, 3, 0, 0, tzinfo=datetime.timezone.utc)

    Cost: O(1)
    """
    if not isinstance(timestamp_input, (int, float)):
        raise TypeError("Input 'timestamp_input' must be an integer or a float.")

    try:
        # datetime.fromtimestamp() creates a datetime object from a Unix timestamp.
        # It's important to specify tz=timezone.utc to get a UTC-aware datetime,
        # otherwise, it defaults to the local timezone, which can be ambiguous.
        return datetime.fromtimestamp(timestamp_input, tz=timezone.utc)
    except ValueError as e:
        raise ValueError(f"Invalid timestamp value '{timestamp_input}': {e}") from e

unix_epoch_day(d: Union[date, datetime]) -> int

Returns the ordinal day number since the Unix epoch (1970-01-01 = day 0).

Parameters:

Name Type Description Default
d Union[date, datetime]

A date or datetime object.

required

Returns:

Type Description
int

Integer day count since 1970-01-01.

Raises:

Type Description
TypeError

If d is not a date or datetime.

Example

from datetime import date unix_epoch_day(date(1970, 1, 1)) 0 unix_epoch_day(date(2026, 4, 8)) 20552

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def unix_epoch_day(d: Union[date, datetime]) -> int:
    """Returns the ordinal day number since the Unix epoch (1970-01-01 = day 0).

    Args:
        d: A date or datetime object.

    Returns:
        Integer day count since 1970-01-01.

    Raises:
        TypeError: If d is not a date or datetime.

    Example:
        >>> from datetime import date
        >>> unix_epoch_day(date(1970, 1, 1))
        0
        >>> unix_epoch_day(date(2026, 4, 8))
        20552

    Complexity: O(1)
    """
    if not isinstance(d, (date, datetime)):
        raise TypeError("d must be a date or datetime")

    dt_date = d.date() if isinstance(d, datetime) else d
    return (dt_date - date(1970, 1, 1)).days

unix_epoch_days(d: Union[date, datetime]) -> int

Days elapsed since the Unix epoch (1970-01-01).

Parameters:

Name Type Description Default
d Union[date, datetime]

Date or datetime to convert.

required

Returns:

Type Description
int

Number of days (negative for dates before 1970-01-01).

Raises:

Type Description
TypeError

If d is not a date or datetime.

Example

unix_epoch_days(date(2000, 1, 1)) 10957

Complexity: O(1)

Source code in shortfx/fxDate/date_convertions.py
def unix_epoch_days(d: Union[date, datetime]) -> int:
    """Days elapsed since the Unix epoch (1970-01-01).

    Args:
        d: Date or datetime to convert.

    Returns:
        Number of days (negative for dates before 1970-01-01).

    Raises:
        TypeError: If *d* is not a date or datetime.

    Example:
        >>> unix_epoch_days(date(2000, 1, 1))
        10957

    Complexity: O(1)
    """
    if not isinstance(d, (date, datetime)):
        raise TypeError("d must be a date or datetime")

    target = d.date() if isinstance(d, datetime) else d
    epoch = date(1970, 1, 1)

    return (target - epoch).days

unix_timestamp_to_datetime(timestamp: Union[int, float], tz_info: Optional[str] = None) -> datetime

Converts a UNIX timestamp (seconds since the Epoch) to a datetime object.

Parameters:

Name Type Description Default
timestamp Union[int, float]

The UNIX timestamp.

required
tz_info Optional[str]

An IANA timezone string (e.g., 'America/New_York', 'Europe/Madrid'). If provided, the resulting datetime will be "aware" (with timezone). If None, the datetime will be "naive" (without timezone, assuming local).

None

Returns:

Name Type Description
datetime datetime

The resulting datetime object.

Raises:

Type Description
TypeError

If 'timestamp' is not an int or float, or if 'tz_info' is not a string.

ZoneInfoNotFoundError

If the 'tz_info' string doesn't correspond to a valid timezone.

Example

from datetime import datetime

Convert a timestamp to UTC datetime

unix_timestamp_to_datetime(1718092025.0, tz_info='UTC') datetime.datetime(2025, 6, 11, 10, 27, 5, tzinfo=datetime.timezone.utc)

Convert to datetime in Madrid timezone

madrid_dt = unix_timestamp_to_datetime(1718092025.0, tz_info='Europe/Madrid') madrid_dt # Note the +02:00 offset for CEST datetime.datetime(2025, 6, 11, 12, 27, 5, tzinfo=zoneinfo.ZoneInfo(key='Europe/Madrid'))

Convert to naive datetime (local time)

unix_timestamp_to_datetime(1718092025.0) datetime.datetime(2025, 6, 11, 12, 27, 5) # Output assumes local timezone is CEST+2 for this example

Cost: O(1), direct conversion from timestamp to datetime.

Source code in shortfx/fxDate/date_convertions.py
def unix_timestamp_to_datetime(timestamp: Union[int, float], tz_info: Optional[str] = None) -> datetime:
    """
    Converts a UNIX timestamp (seconds since the Epoch) to a datetime object.

    Args:
        timestamp (Union[int, float]): The UNIX timestamp.
        tz_info (Optional[str]): An IANA timezone string (e.g., 'America/New_York', 'Europe/Madrid').
                                 If provided, the resulting datetime will be "aware" (with timezone).
                                 If None, the datetime will be "naive" (without timezone, assuming local).

    Returns:
        datetime: The resulting datetime object.

    Raises:
        TypeError: If 'timestamp' is not an int or float, or if 'tz_info' is not a string.
        zoneinfo.ZoneInfoNotFoundError: If the 'tz_info' string doesn't correspond to a valid timezone.

    Example:
        >>> from datetime import datetime
        >>> # Convert a timestamp to UTC datetime
        >>> unix_timestamp_to_datetime(1718092025.0, tz_info='UTC')
        datetime.datetime(2025, 6, 11, 10, 27, 5, tzinfo=datetime.timezone.utc)

        >>> # Convert to datetime in Madrid timezone
        >>> madrid_dt = unix_timestamp_to_datetime(1718092025.0, tz_info='Europe/Madrid')
        >>> madrid_dt # Note the +02:00 offset for CEST
        datetime.datetime(2025, 6, 11, 12, 27, 5, tzinfo=zoneinfo.ZoneInfo(key='Europe/Madrid'))

        >>> # Convert to naive datetime (local time)
        >>> unix_timestamp_to_datetime(1718092025.0)
        datetime.datetime(2025, 6, 11, 12, 27, 5) # Output assumes local timezone is CEST+2 for this example

    **Cost:** O(1), direct conversion from timestamp to datetime.
    """
    if not isinstance(timestamp, (int, float)):
        raise TypeError("'timestamp' must be an int or float.")

    target_tz = None
    if tz_info:
        try:
            target_tz = zoneinfo.ZoneInfo(tz_info)
        except ImportError:
            # Fallback for Python < 3.9 or if zoneinfo is not available
            import warnings
            warnings.warn("'zoneinfo' module not found. Timezone awareness might be limited.", stacklevel=2)
            if tz_info.upper() == 'UTC':
                target_tz = timezone.utc
            else:
                # Basic timezone handling if zoneinfo not available (e.g., fixed offset)
                # This is a simplification; full TZ handling needs zoneinfo/pytz
                raise NotImplementedError("Specific timezone conversion without 'zoneinfo' is not fully implemented. Please install 'zoneinfo' or use Python 3.9+ for full timezone support.")
        except zoneinfo.ZoneInfoNotFoundError:
            raise ValueError(f"Invalid or unknown timezone: '{tz_info}'.")

    if target_tz:
        # fromtimestamp with tz argument available in Python 3.3+
        return datetime.fromtimestamp(timestamp, tz=target_tz)
    else:
        # If no tz_info, return a naive datetime from local timestamp
        return datetime.fromtimestamp(timestamp)

utc_to_datetime(date_input: Union[datetime, str], input_format: Optional[str] = None, input_tz: Optional[str] = None) -> datetime

Converts any datetime object (aware or naive) or date string to its equivalent UTC representation.

Problem/User Need: The best practice for storing dates is in UTC to avoid timezone and daylight saving issues. Users need a simple way to normalize any date to UTC before saving or processing it.

Product Goals: Promote consistency in date storage and processing, simplifying business logic and preventing timezone-related errors.

Description: Given a date input, this function performs the following: 1. If date_input is a string, it parses it into a datetime object. 2. If the resulting datetime object is naive (has no timezone info): a. If input_tz is provided, it assumes the naive datetime is in that timezone. b. If input_tz is NOT provided, it assumes the naive datetime is in the system's local timezone. 3. If the resulting datetime object is already timezone-aware, it uses its existing timezone information. 4. Finally, it converts the datetime object to UTC.

Parameters:

Name Type Description Default
date_input Union[datetime, str]

The date to convert to UTC. Can be a datetime object (e.g., datetime(2025, 6, 11, 10, 0, 0)) or a string (e.g., "2025-06-11 10:00:00").

required
input_format Optional[str]

The format code string for date_input if it's a string. This is required if date_input is a string. Example: '%Y-%m-%d %H:%M:%S'.

None
input_tz Optional[str]

The IANA timezone name of the date_input if it's a naive datetime object or a string that doesn't contain timezone info. If None and date_input is naive, the system's local timezone is assumed. Example: 'America/Los_Angeles', 'Europe/Madrid'.

None

Returns:

Name Type Description
datetime datetime

A timezone-aware datetime object representing the input date and time in UTC.

Raises:

Type Description
TypeError

If date_input is not a datetime object or a string.

ValueError

If date_input is a string and input_format is not provided, or if the string cannot be parsed with the given format. If input_tz is provided but is not a recognized timezone. If the local timezone cannot be determined.

Example

from datetime import datetime, timezone import zoneinfo

Scenario 1: Input is a naive datetime object (system local time is CEST = UTC+2)
Assuming the system's local timezone is Europe/Madrid (currently CEST, UTC+2)

naive_dt_local = datetime(2025, 6, 11, 10, 0, 0) # 10:00 AM local time utc_dt = utc_to_datetime(naive_dt_local) print(f"Naive local (10:00 CEST) -> UTC: {utc_dt}")

Expected: 2025-06-11 08:00:00+00:00 (10:00 CEST - 2 hours = 08:00 UTC)
Scenario 2: Input is an aware datetime object (e.g., in 'America/Los_Angeles')

la_tz = zoneinfo.ZoneInfo('America/Los_Angeles') aware_dt_la = datetime(2025, 6, 11, 10, 0, 0, tzinfo=la_tz) # 10:00 AM in LA (PDT, UTC-7) utc_dt = utc_to_datetime(aware_dt_la) print(f"Aware LA (10:00 PDT) -> UTC: {utc_dt}")

Expected: 2025-06-11 17:00:00+00:00 (10:00 PDT + 7 hours = 17:00 UTC)
Scenario 3: Input is a string, and we specify its original timezone
Date string "2025-06-11 10:00:00" is in 'Asia/Tokyo' (JST, UTC+9)

utc_dt = utc_to_datetime("2025-06-11 10:00:00", "%Y-%m-%d %H:%M:%S", "Asia/Tokyo") print(f"String Tokyo (10:00 JST) -> UTC: {utc_dt}")

Expected: 2025-06-11 01:00:00+00:00 (10:00 JST - 9 hours = 01:00 UTC)
Scenario 4: Input is a string with no explicit timezone provided, assumes local
Assuming local system is CEST (UTC+2)

utc_dt = utc_to_datetime("2025-06-11 10:00:00", "%Y-%m-%d %H:%M:%S") print(f"String local (10:00 CEST) -> UTC: {utc_dt}")

Expected: 2025-06-11 08:00:00+00:00

Cost: O(1)

Source code in shortfx/fxDate/date_convertions.py
def utc_to_datetime(
    date_input: Union[datetime, str],
    input_format: Optional[str] = None,
    input_tz: Optional[str] = None
) -> datetime:
    """Converts any datetime object (aware or naive) or date string to its equivalent UTC representation.

    Problem/User Need: The best practice for storing dates is in UTC to avoid
    timezone and daylight saving issues. Users need a simple way to normalize
    any date to UTC before saving or processing it.

    Product Goals: Promote consistency in date storage and processing, simplifying
    business logic and preventing timezone-related errors.

    Description: Given a date input, this function performs the following:
    1. If `date_input` is a string, it parses it into a datetime object.
    2. If the resulting datetime object is naive (has no timezone info):
       a. If `input_tz` is provided, it assumes the naive datetime is in that timezone.
       b. If `input_tz` is NOT provided, it assumes the naive datetime is in the
          system's **local timezone**.
    3. If the resulting datetime object is already timezone-aware, it uses its
       existing timezone information.
    4. Finally, it converts the datetime object to UTC.

    Args:
        date_input (Union[datetime, str]): The date to convert to UTC.
                                            Can be a datetime object (e.g., `datetime(2025, 6, 11, 10, 0, 0)`)
                                            or a string (e.g., `"2025-06-11 10:00:00"`).
        input_format (Optional[str], optional): The format code string for `date_input` if it's a string.
                                                This is **required** if `date_input` is a string.
                                                Example: `'%Y-%m-%d %H:%M:%S'`.
        input_tz (Optional[str], optional): The IANA timezone name of the `date_input`
                                            if it's a naive `datetime` object or a string
                                            that doesn't contain timezone info. If `None`
                                            and `date_input` is naive, the system's local
                                            timezone is assumed. Example: `'America/Los_Angeles'`, `'Europe/Madrid'`.

    Returns:
        datetime: A timezone-aware datetime object representing the input date
                  and time in UTC.

    Raises:
        TypeError: If `date_input` is not a `datetime` object or a string.
        ValueError: If `date_input` is a string and `input_format` is not provided,
                    or if the string cannot be parsed with the given format.
                    If `input_tz` is provided but is not a recognized timezone.
                    If the local timezone cannot be determined.

    Example:
        >>> from datetime import datetime, timezone
        >>> import zoneinfo

        >>> # Scenario 1: Input is a naive datetime object (system local time is CEST = UTC+2)
        >>> # Assuming the system's local timezone is Europe/Madrid (currently CEST, UTC+2)
        >>> naive_dt_local = datetime(2025, 6, 11, 10, 0, 0) # 10:00 AM local time
        >>> utc_dt = utc_to_datetime(naive_dt_local)
        >>> print(f"Naive local (10:00 CEST) -> UTC: {utc_dt}")
        # Expected: 2025-06-11 08:00:00+00:00 (10:00 CEST - 2 hours = 08:00 UTC)

        >>> # Scenario 2: Input is an aware datetime object (e.g., in 'America/Los_Angeles')
        >>> la_tz = zoneinfo.ZoneInfo('America/Los_Angeles')
        >>> aware_dt_la = datetime(2025, 6, 11, 10, 0, 0, tzinfo=la_tz) # 10:00 AM in LA (PDT, UTC-7)
        >>> utc_dt = utc_to_datetime(aware_dt_la)
        >>> print(f"Aware LA (10:00 PDT) -> UTC: {utc_dt}")
        # Expected: 2025-06-11 17:00:00+00:00 (10:00 PDT + 7 hours = 17:00 UTC)

        >>> # Scenario 3: Input is a string, and we specify its original timezone
        >>> # Date string "2025-06-11 10:00:00" is in 'Asia/Tokyo' (JST, UTC+9)
        >>> utc_dt = utc_to_datetime("2025-06-11 10:00:00", "%Y-%m-%d %H:%M:%S", "Asia/Tokyo")
        >>> print(f"String Tokyo (10:00 JST) -> UTC: {utc_dt}")
        # Expected: 2025-06-11 01:00:00+00:00 (10:00 JST - 9 hours = 01:00 UTC)

        >>> # Scenario 4: Input is a string with no explicit timezone provided, assumes local
        >>> # Assuming local system is CEST (UTC+2)
        >>> utc_dt = utc_to_datetime("2025-06-11 10:00:00", "%Y-%m-%d %H:%M:%S")
        >>> print(f"String local (10:00 CEST) -> UTC: {utc_dt}")
        # Expected: 2025-06-11 08:00:00+00:00

    Cost: O(1)
    """
    # 1. Parse the input into a datetime object
    parsed_dt: datetime
    if isinstance(date_input, str):
        if input_format is None:
            raise ValueError("'input_format' is required when 'date_input' is a string.")
        try:
            parsed_dt = datetime.strptime(date_input, input_format)
        except ValueError as e:
            raise ValueError(f"Could not parse date string '{date_input}' with format '{input_format}'. Error: {e}") from e
    elif isinstance(date_input, datetime):
        parsed_dt = date_input
    else:
        raise TypeError("Input 'date_input' must be a datetime object or a string.")

    # 2. Make the datetime object timezone-aware if it's naive
    if parsed_dt.tzinfo is None:
        # Determine the timezone to assume for the naive input
        assumed_tz: zoneinfo.ZoneInfo
        if input_tz:
            try:
                assumed_tz = zoneinfo.ZoneInfo(input_tz)
            except zoneinfo.ZoneInfoNotFoundError:
                raise ValueError(f"Specified 'input_tz' timezone '{input_tz}' not found.")
        else:
            # If no input_tz is provided, assume the system's local timezone.
            try:
                assumed_tz = zoneinfo.ZoneInfo("localtime")
            except zoneinfo.ZoneInfoNotFoundError:
                raise ValueError(
                    "Could not determine local timezone for naive input. "
                    "Please provide 'input_tz' or ensure system timezone is configured."
                )

        # Localize the naive datetime to the assumed timezone
        parsed_dt = parsed_dt.replace(tzinfo=assumed_tz)
        # Note: For historical dates and timezones with complex rules (like DST transitions),
        # using `assumed_tz.localize(parsed_dt)` from dateutil or pytz is sometimes
        # more robust than `.replace(tzinfo=...)` for ambiguity resolution.
        # However, for general normalization, .replace() followed by .astimezone() is often sufficient.

    # 3. Convert the (now timezone-aware) datetime object to UTC
    # .astimezone(timezone.utc) ensures it's converted to UTC.
    return parsed_dt.astimezone(timezone.utc)

utc_to_midnight_iso(p_datetime: datetime) -> datetime

Convierte un datetime a su equivalente a medianoche UTC.

Problem/User Need: Es común necesitar normalizar fechas a medianoche UTC para comparaciones consistentes o para asegurar que todos los eventos empiecen al inicio del día.

Product Goals: Proporcionar una forma sencilla de normalizar cualquier datetime a medianoche UTC manteniendo la fecha.

Parameters:

Name Type Description Default
p_datetime datetime

El objeto datetime a convertir.

required

Returns:

Name Type Description
datetime datetime

Un nuevo objeto datetime establecido a medianoche UTC (00:00:00) para la fecha dada.

Raises:

Type Description
TypeError

Si p_datetime no es un objeto datetime.

Example

from datetime import datetime, timezone

Datetime con hora específica

dt = datetime(2025, 6, 11, 15, 30, 45, tzinfo=timezone.utc) midnight_dt = utc_to_midnight_iso(dt) print(midnight_dt) 2025-06-11 00:00:00+00:00

Datetime sin zona horaria

naive_dt = datetime(2025, 6, 11, 15, 30, 45) midnight_dt = utc_to_midnight_iso(naive_dt) print(midnight_dt) 2025-06-11 00:00:00+00:00

Cost: O(1)

Source code in shortfx/fxDate/date_convertions.py
def utc_to_midnight_iso(p_datetime: datetime) -> datetime:
    """Convierte un datetime a su equivalente a medianoche UTC.

    Problem/User Need: Es común necesitar normalizar fechas a medianoche UTC 
    para comparaciones consistentes o para asegurar que todos los eventos 
    empiecen al inicio del día.

    Product Goals: Proporcionar una forma sencilla de normalizar cualquier 
    datetime a medianoche UTC manteniendo la fecha.

    Args:
        p_datetime (datetime): El objeto datetime a convertir.

    Returns:
        datetime: Un nuevo objeto datetime establecido a medianoche UTC (00:00:00)
                 para la fecha dada.

    Raises:
        TypeError: Si p_datetime no es un objeto datetime.

    Example:
        >>> from datetime import datetime, timezone
        >>> # Datetime con hora específica
        >>> dt = datetime(2025, 6, 11, 15, 30, 45, tzinfo=timezone.utc)
        >>> midnight_dt = utc_to_midnight_iso(dt)
        >>> print(midnight_dt)
        2025-06-11 00:00:00+00:00

        >>> # Datetime sin zona horaria
        >>> naive_dt = datetime(2025, 6, 11, 15, 30, 45)
        >>> midnight_dt = utc_to_midnight_iso(naive_dt)
        >>> print(midnight_dt)
        2025-06-11 00:00:00+00:00

    Cost: O(1)
    """
    if not isinstance(p_datetime, datetime):
        raise TypeError("Input must be a datetime object")

    # Si el datetime es naive (sin zona horaria), asumimos que es UTC
    if p_datetime.tzinfo is None:
        p_datetime = p_datetime.replace(tzinfo=timezone.utc)
    else:
        # Si tiene zona horaria, convertimos a UTC
        p_datetime = p_datetime.astimezone(timezone.utc)

    # Establecer la hora a medianoche manteniendo la fecha y la zona UTC
    midnight_datetime = p_datetime.replace(
        hour=0, 
        minute=0, 
        second=0, 
        microsecond=0
    )

    return midnight_datetime

utc_to_timezone(input_datetime: datetime, target_tz_name: str = 'Europe/Madrid') -> datetime

Localizes a naive datetime object to the target timezone or validates an aware datetime, then returns it localized to the target timezone.

This function is used when you have a datetime that might be naive (without tzinfo)
and you need to assume a specific timezone for it, or when you have an aware datetime
and want to ensure it's converted to the target timezone. If the input datetime is naive,
it will be localized directly to the `target_tz_name`. If it's already timezone-aware,
it will be converted to the `target_tz_name`.

Args:
    input_datetime (datetime): The datetime object to process. If it's naive,
                               it will be localized to `target_tz_name`. If it's
                               already timezone-aware, it will be converted
                               to `target_tz_name`.
    target_tz_name (str): The name of the target timezone to localize/convert to (e.g.,
                           'America/New_York', 'Europe/Madrid', 'Asia/Tokyo').
                           These are typically IANA timezone database names.

Returns:
    datetime: A new timezone-aware datetime object representing the same moment in time,
              but localized to the `target_tz_name`.

Raises:
    TypeError: If `input_datetime` is not a datetime object.
    zoneinfo.ZoneInfoNotFoundError: If `target_tz_name` is not a recognized timezone
                                    in the system's timezone database.

Example:
    >>> from datetime import datetime, timezone
    >>> import zoneinfo

    >>> # 1. Define a target timezone (e.g., Europe/Madrid)
    >>> madrid_tz = zoneinfo.ZoneInfo('Europe/Madrid')

    >>> # 2. Define a naive datetime
    >>> naive_dt = datetime(2025, 6, 11, 10, 0, 0) # 10:00 AM, but in which timezone?
    >>> print(f"Original Naive: {naive_dt}")

    >>> # Localize naive_dt directly to Madrid time (assumes 10:00 AM Madrid time)
    >>> localized_to_madrid = utc_to_timezone(naive_dt, 'Europe/Madrid')
    >>> print(f"Localized to Madrid: {localized_to_madrid}")
    # Expected: 2025-06-11 10:00:00+02:00 (since June in Madrid is UTC+2)

    >>> # Now convert the Madrid-localized time to New York time (PDT = UTC-4 in June for NY)
    >>> madrid_to_ny = utc_to_timezone(localized_to_madrid, 'America/New_York')
    >>> print(f"Madrid time converted to New York: {madrid_to_ny}")
    # Expected: 2025-06-11 04:00:00-04:00 (10:00 Madrid is 08:00 UTC, which is 04:00 NY)

    >>> # 3. Define an already aware datetime (e.g., in UTC)
    >>> utc_aware_dt = datetime(2025, 6, 11, 8, 0, 0, tzinfo=timezone.utc) # 8:00 AM UTC
    >>> print(f"

Original UTC Aware: {utc_aware_dt}")

    >>> # Convert the UTC aware datetime to Madrid time
    >>> utc_to_madrid = utc_to_timezone(utc_aware_dt, 'Europe/Madrid')
    >>> print(f"UTC Aware converted to Madrid: {utc_to_madrid}")
    # Expected: 2025-06-11 10:00:00+02:00

Cost: O(1)
Source code in shortfx/fxDate/date_convertions.py
def utc_to_timezone(input_datetime: datetime, target_tz_name: str = "Europe/Madrid") -> datetime:
    """Localizes a naive datetime object to the target timezone or validates an aware datetime,
    then returns it localized to the target timezone.

    This function is used when you have a datetime that might be naive (without tzinfo)
    and you need to assume a specific timezone for it, or when you have an aware datetime
    and want to ensure it's converted to the target timezone. If the input datetime is naive,
    it will be localized directly to the `target_tz_name`. If it's already timezone-aware,
    it will be converted to the `target_tz_name`.

    Args:
        input_datetime (datetime): The datetime object to process. If it's naive,
                                   it will be localized to `target_tz_name`. If it's
                                   already timezone-aware, it will be converted
                                   to `target_tz_name`.
        target_tz_name (str): The name of the target timezone to localize/convert to (e.g.,
                               'America/New_York', 'Europe/Madrid', 'Asia/Tokyo').
                               These are typically IANA timezone database names.

    Returns:
        datetime: A new timezone-aware datetime object representing the same moment in time,
                  but localized to the `target_tz_name`.

    Raises:
        TypeError: If `input_datetime` is not a datetime object.
        zoneinfo.ZoneInfoNotFoundError: If `target_tz_name` is not a recognized timezone
                                        in the system's timezone database.

    Example:
        >>> from datetime import datetime, timezone
        >>> import zoneinfo

        >>> # 1. Define a target timezone (e.g., Europe/Madrid)
        >>> madrid_tz = zoneinfo.ZoneInfo('Europe/Madrid')

        >>> # 2. Define a naive datetime
        >>> naive_dt = datetime(2025, 6, 11, 10, 0, 0) # 10:00 AM, but in which timezone?
        >>> print(f"Original Naive: {naive_dt}")

        >>> # Localize naive_dt directly to Madrid time (assumes 10:00 AM Madrid time)
        >>> localized_to_madrid = utc_to_timezone(naive_dt, 'Europe/Madrid')
        >>> print(f"Localized to Madrid: {localized_to_madrid}")
        # Expected: 2025-06-11 10:00:00+02:00 (since June in Madrid is UTC+2)

        >>> # Now convert the Madrid-localized time to New York time (PDT = UTC-4 in June for NY)
        >>> madrid_to_ny = utc_to_timezone(localized_to_madrid, 'America/New_York')
        >>> print(f"Madrid time converted to New York: {madrid_to_ny}")
        # Expected: 2025-06-11 04:00:00-04:00 (10:00 Madrid is 08:00 UTC, which is 04:00 NY)

        >>> # 3. Define an already aware datetime (e.g., in UTC)
        >>> utc_aware_dt = datetime(2025, 6, 11, 8, 0, 0, tzinfo=timezone.utc) # 8:00 AM UTC
        >>> print(f"\nOriginal UTC Aware: {utc_aware_dt}")

        >>> # Convert the UTC aware datetime to Madrid time
        >>> utc_to_madrid = utc_to_timezone(utc_aware_dt, 'Europe/Madrid')
        >>> print(f"UTC Aware converted to Madrid: {utc_to_madrid}")
        # Expected: 2025-06-11 10:00:00+02:00

    Cost: O(1)
    """
    if not isinstance(input_datetime, datetime):
        raise TypeError("Input 'input_datetime' must be a datetime object.")

    # 1. Get the target timezone object
    try:
        target_tz = zoneinfo.ZoneInfo(target_tz_name)
    except zoneinfo.ZoneInfoNotFoundError:
        raise zoneinfo.ZoneInfoNotFoundError(
            f"Timezone '{target_tz_name}' not found. "
            "Please use a valid IANA timezone name (e.g., 'America/New_York', 'Europe/London')."
        )

    # If the datetime object is naive (tzinfo is None), localize it directly to the target timezone.
    # This means we are assuming that the naive datetime represents a time in `target_tz_name`.
    if input_datetime.tzinfo is None:
        return input_datetime.replace(tzinfo=target_tz)
    # If it's already timezone-aware, convert it to the target timezone.
    else:
        return input_datetime.astimezone(target_tz)