Pular para conteúdo

Ferramentas de Dias Úteis

count(start, end)

Conta dias úteis entre start (inclusivo) e end (exclusivo).

Considera feriados brasileiros com seleção de regime de feriados por elemento.

PRESERVAÇÃO DE ORDEM (crítico): A ordem de saída SEMPRE corresponde à ordem elemento a elemento das entradas originais. Nenhuma ordenação, deduplicação, alinhamento ou remodelação é realizada. Se você passar arrays, o i-ésimo resultado corresponde ao i-ésimo par de (start, end) após expansão. Isso garante atribuição segura de volta ao DataFrame de origem.

Regime de feriados: Para cada valor de start, a lista de feriados (antiga vs. nova) é escolhida com base na data de transição 2023-12-26 (DATA_TRANSICAO). Datas de início antes da transição usam a lista antiga; datas na transição ou após usam a lista nova.

Propagação de nulos: Se qualquer argumento escalar for nulo, retorna None. Nulos dentro de arrays de entrada produzem nulos nas posições correspondentes do resultado.

Tipo de retorno: Se ambas as entradas forem escalares (não-nulos), um int é retornado; caso contrário, uma polars.Series de contagens inteiras (nome: 'bday_count'). Se um escalar nulo causar curto-circuito, None é retornado.

Parameters:

Name Type Description Default
start None | DateLike | ArrayLike

Data única ou coleção (limite inclusivo).

required
end None | DateLike | ArrayLike

Data única ou coleção (limite exclusivo).

required

Returns:

Type Description
None | int | Series

Inteiro ou None se start e end forem datas únicas, ou Series

None | int | Series

se qualquer um deles for um array de datas.

Notes
  • Esta função é um encapsulamento de polars.business_day_count.
  • A lista de feriados é determinada por linha com base na data start.
  • Strings de data aceitas: DD-MM-YYYY, DD/MM/YYYY e YYYY-MM-DD.
  • Strings inválidas são tratadas como null e propagadas ao resultado.

Examples:

>>> from pyield import bday
>>> bday.count("15-12-2023", "01-01-2024")
10

Contagem negativa quando start é posterior a end:

>>> bday.count("08-01-2023", "01-01-2023")
-5

Total de dias úteis em janeiro e fevereiro desde o início do ano:

>>> bday.count(start="01-01-2024", end=["01-02-2024", "01-03-2024"])
shape: (2,)
Series: 'bday_count' [i64]
[
    22
    41
]

Dias úteis restantes de janeiro/fevereiro até o fim do ano:

>>> bday.count(["01-01-2024", "01-02-2024"], "01-01-2025")
shape: (2,)
Series: 'bday_count' [i64]
[
    253
    231
]

Total de dias úteis em janeiro e fevereiro de 2024:

>>> bday.count(["01-01-2024", "01-02-2024"], ["01-02-2024", "01-03-2024"])
shape: (2,)
Series: 'bday_count' [i64]
[
    22
    19
]

Valores nulos são propagados:

>>> bday.count(None, "01-01-2024")  # None em start
>>> bday.count("01-01-2024", None)  # None em end
>>> bday.count("01-01-2024", ["01-02-2024", None])  # None dentro do array
shape: (2,)
Series: 'bday_count' [i64]
[
    22
    null
]
>>> start_dates = ["01-01-2024", "01-02-2024", "01-03-2024"]
>>> bday.count(start_dates, "01-01-2025")
shape: (3,)
Series: 'bday_count' [i64]
[
    253
    231
    212
]
Source code in pyield/bday/core.py
def count(
    start: None | DateLike | ArrayLike,
    end: None | DateLike | ArrayLike,
) -> None | int | pl.Series:
    """Conta dias úteis entre ``start`` (inclusivo) e ``end`` (exclusivo).

    Considera feriados brasileiros com seleção de regime de feriados por elemento.

    PRESERVAÇÃO DE ORDEM (crítico): A ordem de saída SEMPRE corresponde à ordem
    elemento a elemento das entradas originais. Nenhuma ordenação, deduplicação,
    alinhamento ou remodelação é realizada. Se você passar arrays, o i-ésimo
    resultado corresponde ao i-ésimo par de (``start``, ``end``) após expansão.
    Isso garante atribuição segura de volta ao DataFrame de origem.

    Regime de feriados: Para cada valor de ``start``, a lista de feriados (antiga vs.
    nova) é escolhida com base na data de transição 2023-12-26 (``DATA_TRANSICAO``).
    Datas de início antes da transição usam a lista antiga; datas na transição ou
    após usam a lista nova.

    Propagação de nulos: Se qualquer argumento escalar for nulo, retorna ``None``.
    Nulos dentro de arrays de entrada produzem nulos nas posições correspondentes
    do resultado.

    Tipo de retorno: Se ambas as entradas forem escalares (não-nulos), um ``int``
    é retornado; caso contrário, uma ``polars.Series`` de contagens inteiras
    (nome: 'bday_count'). Se um escalar nulo causar curto-circuito, ``None`` é
    retornado.

    Args:
        start: Data única ou coleção (limite inclusivo).
        end: Data única ou coleção (limite exclusivo).

    Returns:
        Inteiro ou ``None`` se ``start`` e ``end`` forem datas únicas, ou Series
        se qualquer um deles for um array de datas.

    Notes:
        - Esta função é um encapsulamento de ``polars.business_day_count``.
        - A lista de feriados é determinada por linha com base na data ``start``.
        - Strings de data aceitas: ``DD-MM-YYYY``, ``DD/MM/YYYY`` e ``YYYY-MM-DD``.
        - Strings inválidas são tratadas como ``null`` e propagadas ao resultado.

    Examples:
        >>> from pyield import bday
        >>> bday.count("15-12-2023", "01-01-2024")
        10

        Contagem negativa quando ``start`` é posterior a ``end``:
        >>> bday.count("08-01-2023", "01-01-2023")
        -5

        Total de dias úteis em janeiro e fevereiro desde o início do ano:
        >>> bday.count(start="01-01-2024", end=["01-02-2024", "01-03-2024"])
        shape: (2,)
        Series: 'bday_count' [i64]
        [
            22
            41
        ]

        Dias úteis restantes de janeiro/fevereiro até o fim do ano:
        >>> bday.count(["01-01-2024", "01-02-2024"], "01-01-2025")
        shape: (2,)
        Series: 'bday_count' [i64]
        [
            253
            231
        ]

        Total de dias úteis em janeiro e fevereiro de 2024:
        >>> bday.count(["01-01-2024", "01-02-2024"], ["01-02-2024", "01-03-2024"])
        shape: (2,)
        Series: 'bday_count' [i64]
        [
            22
            19
        ]

        Valores nulos são propagados:
        >>> bday.count(None, "01-01-2024")  # None em start

        >>> bday.count("01-01-2024", None)  # None em end

        >>> bday.count("01-01-2024", ["01-02-2024", None])  # None dentro do array
        shape: (2,)
        Series: 'bday_count' [i64]
        [
            22
            null
        ]

        >>> start_dates = ["01-01-2024", "01-02-2024", "01-03-2024"]
        >>> bday.count(start_dates, "01-01-2025")
        shape: (3,)
        Series: 'bday_count' [i64]
        [
            253
            231
            212
        ]
    """
    s = (
        pl.DataFrame(
            data={"start": start, "end": end},
            nan_to_null=True,
        )
        .select(bday_count=count_expr("start", "end"))
        .get_column("bday_count")
    )

    if not tp.any_is_collection(start, end):
        return s.item()

    return s

count_expr(start, end)

Cria uma expressão Polars para contar dias úteis (com suporte a LazyFrame).

Esta função foi projetada para ser usada dentro de contextos do Polars, como df.select(), df.with_columns() ou df.filter().

Parameters:

Name Type Description Default
start Expr | str | date

Nome da coluna, expressão Polars ou data literal.

required
end Expr | str | date

Nome da coluna, expressão Polars ou data literal.

required

Returns:

Type Description
Expr

Uma pl.Expr que resulta em Int64.

Examples:

>>> import polars as pl
>>> from pyield.bday import count_expr
>>> start = [dt.date(2024, 1, 1), dt.date(2024, 2, 9)]
>>> end = [dt.date(2024, 1, 5), dt.date(2024, 2, 12)]
>>> df = pl.DataFrame({"start": start, "end": end})
>>> df.select(count_expr("start", "end").alias("bdays"))
shape: (2, 1)
┌───────┐
│ bdays │
│ ---   │
│ i64   │
╞═══════╡
│ 3     │
│ 1     │
└───────┘

Uso com literais (ex: contar dias até o fim do ano):

>>> df.select(bdays=count_expr("start", dt.date(2024, 12, 31)))
shape: (2, 1)
┌───────┐
│ bdays │
│ ---   │
│ i64   │
╞═══════╡
│ 252   │
│ 224   │
└───────┘
Source code in pyield/bday/core.py
def count_expr(start: pl.Expr | str | dt.date, end: pl.Expr | str | dt.date) -> pl.Expr:
    """Cria uma expressão Polars para contar dias úteis (com suporte a LazyFrame).

    Esta função foi projetada para ser usada dentro de contextos do Polars,
    como ``df.select()``, ``df.with_columns()`` ou ``df.filter()``.

    Args:
        start: Nome da coluna, expressão Polars ou data literal.
        end: Nome da coluna, expressão Polars ou data literal.

    Returns:
        Uma ``pl.Expr`` que resulta em Int64.

    Examples:
        >>> import polars as pl
        >>> from pyield.bday import count_expr
        >>> start = [dt.date(2024, 1, 1), dt.date(2024, 2, 9)]
        >>> end = [dt.date(2024, 1, 5), dt.date(2024, 2, 12)]
        >>> df = pl.DataFrame({"start": start, "end": end})
        >>> df.select(count_expr("start", "end").alias("bdays"))
        shape: (2, 1)
        ┌───────┐
        │ bdays │
        │ ---   │
        │ i64   │
        ╞═══════╡
        │ 3     │
        │ 1     │
        └───────┘

        Uso com literais (ex: contar dias até o fim do ano):
        >>> df.select(bdays=count_expr("start", dt.date(2024, 12, 31)))
        shape: (2, 1)
        ┌───────┐
        │ bdays │
        │ ---   │
        │ i64   │
        ╞═══════╡
        │ 252   │
        │ 224   │
        └───────┘
    """
    if isinstance(start, dt.date):
        start_date = pl.lit(start)
    else:
        start_date = cv.converter_datas_expr(start)

    if isinstance(end, dt.date):
        end_date = pl.lit(end)
    else:
        end_date = cv.converter_datas_expr(end)

    return pl.business_day_count(
        start=start_date,
        end=end_date,
        holidays=_expressao_feriados(start_date),
    ).cast(pl.Int64)

generate(start=None, end=None, closed='both', holiday_option='new')

Gera uma Series de dias úteis entre start e end.

Considera a lista de feriados brasileiros.

Parameters:

Name Type Description Default
start DateLike | None

Data inicial. Se None, usa a data atual.

None
end DateLike | None

Data final. Se None, usa a data atual.

None
closed Literal['both', 'left', 'right', 'none']

Define quais lados do intervalo são fechados (inclusivos). Opções válidas: 'both', 'left', 'right', 'none'. Padrão: 'both'.

'both'
holiday_option Literal['old', 'new', 'infer']

Especifica a lista de feriados a considerar. Padrão: "new". - 'old': Usa a lista de feriados vigente antes de 2023-12-26. - 'new': Usa a lista de feriados vigente a partir de 2023-12-26. - 'infer': Seleciona com base na data start relativa à transição.

'new'

Returns:

Type Description
Series

Series de dias úteis (nome: 'bday').

Notes
  • Strings de data aceitas: DD-MM-YYYY, DD/MM/YYYY e YYYY-MM-DD.
  • start e end nulos usam a data atual.

Examples:

>>> from pyield import bday
>>> bday.generate(start="22-12-2023", end="02-01-2024")
shape: (6,)
Series: 'bday' [date]
[
    2023-12-22
    2023-12-26
    2023-12-27
    2023-12-28
    2023-12-29
    2024-01-02
]
Source code in pyield/bday/core.py
def generate(
    start: DateLike | None = None,
    end: DateLike | None = None,
    closed: Literal["both", "left", "right", "none"] = "both",
    holiday_option: Literal["old", "new", "infer"] = "new",
) -> pl.Series:
    """Gera uma Series de dias úteis entre ``start`` e ``end``.

    Considera a lista de feriados brasileiros.

    Args:
        start: Data inicial. Se None, usa a data atual.
        end: Data final. Se None, usa a data atual.
        closed: Define quais lados do intervalo são fechados (inclusivos).
            Opções válidas: 'both', 'left', 'right', 'none'. Padrão: 'both'.
        holiday_option: Especifica a lista de feriados a considerar. Padrão: "new".
            - 'old': Usa a lista de feriados vigente antes de 2023-12-26.
            - 'new': Usa a lista de feriados vigente a partir de 2023-12-26.
            - 'infer': Seleciona com base na data ``start`` relativa à transição.

    Returns:
        Series de dias úteis (nome: 'bday').

    Notes:
        - Strings de data aceitas: ``DD-MM-YYYY``, ``DD/MM/YYYY`` e ``YYYY-MM-DD``.
        - ``start`` e ``end`` nulos usam a data atual.

    Examples:
        >>> from pyield import bday
        >>> bday.generate(start="22-12-2023", end="02-01-2024")
        shape: (6,)
        Series: 'bday' [date]
        [
            2023-12-22
            2023-12-26
            2023-12-27
            2023-12-28
            2023-12-29
            2024-01-02
        ]
    """
    today = clock.today()
    conv_start = cv.converter_datas(start) or today
    conv_end = cv.converter_datas(end) or today

    # Gera range completo de datas
    s = pl.date_range(conv_start, conv_end, closed=closed, eager=True).alias("bday")

    # Pega feriados aplicáveis
    feriados = feriados_br.obter_feriados(
        datas=conv_start, opcao_feriado=holiday_option
    )

    # Filtra: só dias úteis (seg-sex e não feriado)
    return s.filter((s.dt.weekday() < LIMITE_DIA_UTIL) & (~s.is_in(feriados)))

is_business_day(dates)

Determina se data(s) são dias úteis brasileiros.

REGIME DE FERIADOS POR LINHA: Para CADA data de entrada, a lista de feriados apropriada ("antiga" vs. "nova") é selecionada comparando com a data de transição 2023-12-26 (DATA_TRANSICAO). Datas estritamente antes da transição usam a lista antiga; datas na transição ou após usam a lista nova. Isso espelha o comportamento de count e offset que aplicam a lógica de regime elemento a elemento.

PRESERVAÇÃO DE ORDEM E FORMA: A saída preserva a ordem original dos elementos. Nenhuma ordenação, deduplicação, remodelação ou alinhamento é realizado; o i-ésimo resultado corresponde à i-ésima data fornecida após expansão (se alguma expansão ocorreu de uma entrada escalar em outro lugar da cadeia de chamadas).

PROPAGAÇÃO DE NULOS: Um argumento escalar nulo faz curto-circuito para None. Valores nulos dentro de entradas array-like produzem nulos nas posições correspondentes da saída.

TIPO DE RETORNO: Se a entrada (não-nula) resolve para um único elemento, um bool Python é retornado. Se esse único elemento for nulo, None é retornado. Caso contrário, uma polars.Series de booleanos nomeada 'is_bday' é produzida.

FINS DE SEMANA: Sábados e domingos nunca são dias úteis independentemente do regime de feriados.

Parameters:

Name Type Description Default
dates None | DateLike | ArrayLike

Data única ou coleção (list/tuple/Polars Series). Pode incluir nulos que propagam. Entrada escalar nula retorna None.

required

Returns:

Type Description
None | bool | Series

True se for dia útil, False caso contrário para entrada escalar;

None | bool | Series

None para entrada escalar nula; ou uma Series Polars de booleanos

None | bool | Series

(nome: 'is_bday') para entradas de array.

Examples:

>>> from pyield import bday
>>> bday.is_business_day("25-12-2023")  # Natal (calendário antigo)
False
>>> bday.is_business_day("20-11-2024")  # Dia Nacional de Zumbi (novo feriado)
False
>>> bday.is_business_day(["22-12-2023", "26-12-2023"])  # Períodos mistos
shape: (2,)
Series: 'is_bday' [bool]
[
    true
    true
]
Notes
  • Data de transição definida em DATA_TRANSICAO.
  • Espelha a lógica por linha usada em count e offset.
  • Fins de semana sempre avaliam como False.
  • Elementos nulos propagam.
  • Strings de data aceitas: DD-MM-YYYY, DD/MM/YYYY e YYYY-MM-DD.
  • Strings inválidas são tratadas como null e propagadas ao resultado.
Source code in pyield/bday/core.py
def is_business_day(dates: None | DateLike | ArrayLike) -> None | bool | pl.Series:
    """Determina se data(s) são dias úteis brasileiros.

    REGIME DE FERIADOS POR LINHA: Para CADA data de entrada, a lista de feriados
    apropriada ("antiga" vs. "nova") é selecionada comparando com a data de
    transição 2023-12-26 (``DATA_TRANSICAO``). Datas estritamente antes da
    transição usam a lista antiga; datas na transição ou após usam a lista nova.
    Isso espelha o comportamento de ``count`` e ``offset`` que aplicam a lógica
    de regime elemento a elemento.

    PRESERVAÇÃO DE ORDEM E FORMA: A saída preserva a ordem original dos elementos.
    Nenhuma ordenação, deduplicação, remodelação ou alinhamento é realizado; o
    i-ésimo resultado corresponde à i-ésima data fornecida após expansão (se
    alguma expansão ocorreu de uma entrada escalar em outro lugar da cadeia
    de chamadas).

    PROPAGAÇÃO DE NULOS: Um argumento escalar nulo faz curto-circuito para ``None``.
    Valores nulos dentro de entradas array-like produzem nulos nas posições
    correspondentes da saída.

    TIPO DE RETORNO: Se a entrada (não-nula) resolve para um único elemento, um
    ``bool`` Python é retornado. Se esse único elemento for nulo, ``None`` é
    retornado. Caso contrário, uma ``polars.Series`` de booleanos nomeada
    ``'is_bday'`` é produzida.

    FINS DE SEMANA: Sábados e domingos nunca são dias úteis independentemente do
    regime de feriados.

    Args:
        dates: Data única ou coleção (list/tuple/Polars Series).
            Pode incluir nulos que propagam. Entrada escalar nula retorna ``None``.

    Returns:
        ``True`` se for dia útil, ``False`` caso contrário para entrada escalar;
        ``None`` para entrada escalar nula; ou uma Series Polars de booleanos
        (nome: ``'is_bday'``) para entradas de array.

    Examples:
        >>> from pyield import bday
        >>> bday.is_business_day("25-12-2023")  # Natal (calendário antigo)
        False
        >>> bday.is_business_day("20-11-2024")  # Dia Nacional de Zumbi (novo feriado)
        False
        >>> bday.is_business_day(["22-12-2023", "26-12-2023"])  # Períodos mistos
        shape: (2,)
        Series: 'is_bday' [bool]
        [
            true
            true
        ]

    Notes:
        - Data de transição definida em ``DATA_TRANSICAO``.
        - Espelha a lógica por linha usada em ``count`` e ``offset``.
        - Fins de semana sempre avaliam como ``False``.
        - Elementos nulos propagam.
        - Strings de data aceitas: ``DD-MM-YYYY``, ``DD/MM/YYYY`` e ``YYYY-MM-DD``.
        - Strings inválidas são tratadas como ``null`` e propagadas ao resultado.
    """
    s = (
        pl.DataFrame({"dates": dates}, nan_to_null=True)
        .select(is_bday=is_business_day_expr("dates"))
        .get_column("is_bday")
    )

    if not tp.any_is_collection(dates):
        return s.item()

    return s

is_business_day_expr(expr)

Cria expressão Polars para verificar se é dia útil (True/False).

Parameters:

Name Type Description Default
expr Expr | str

Coluna de datas ou expressão Polars.

required

Returns:

Type Description
Expr

Uma pl.Expr booleana.

Examples:

>>> import datetime as dt
>>> import polars as pl
>>> from pyield.bday import is_business_day_expr
>>> datas = [dt.date(2023, 12, 25), dt.date(2023, 12, 26)]
>>> df = pl.DataFrame({"data": datas})

Criando uma flag booleana:

>>> df.with_columns(is_bd=is_business_day_expr("data"))
shape: (2, 2)
┌────────────┬───────┐
│ data       ┆ is_bd │
│ ---        ┆ ---   │
│ date       ┆ bool  │
╞════════════╪═══════╡
│ 2023-12-25 ┆ false │
│ 2023-12-26 ┆ true  │
└────────────┴───────┘

Usando para filtrar apenas dias úteis:

>>> df.filter(is_business_day_expr("data"))
shape: (1, 1)
┌────────────┐
│ data       │
│ ---        │
│ date       │
╞════════════╡
│ 2023-12-26 │
└────────────┘
Source code in pyield/bday/core.py
def is_business_day_expr(expr: pl.Expr | str) -> pl.Expr:
    """Cria expressão Polars para verificar se é dia útil (True/False).

    Args:
        expr: Coluna de datas ou expressão Polars.

    Returns:
        Uma ``pl.Expr`` booleana.

    Examples:
        >>> import datetime as dt
        >>> import polars as pl
        >>> from pyield.bday import is_business_day_expr
        >>> datas = [dt.date(2023, 12, 25), dt.date(2023, 12, 26)]
        >>> df = pl.DataFrame({"data": datas})

        Criando uma flag booleana:
        >>> df.with_columns(is_bd=is_business_day_expr("data"))
        shape: (2, 2)
        ┌────────────┬───────┐
        │ data       ┆ is_bd │
        │ ---        ┆ ---   │
        │ date       ┆ bool  │
        ╞════════════╪═══════╡
        │ 2023-12-25 ┆ false │
        │ 2023-12-26 ┆ true  │
        └────────────┴───────┘

        Usando para filtrar apenas dias úteis:
        >>> df.filter(is_business_day_expr("data"))
        shape: (1, 1)
        ┌────────────┐
        │ data       │
        │ ---        │
        │ date       │
        ╞════════════╡
        │ 2023-12-26 │
        └────────────┘
    """
    expr_date = cv.converter_datas_expr(expr)

    return expr_date.dt.is_business_day(holidays=_expressao_feriados(expr_date))

last_business_day()

Retorna o último dia útil no Brasil.

Se a data atual for um dia útil, retorna a data atual. Se for fim de semana ou feriado, retorna o último dia útil antes da data atual.

Returns:

Type Description
date

O último dia útil no Brasil.

Notes
  • A determinação do último dia útil considera a lista de feriados brasileiros correta (antes ou depois da transição 2023-12-26) aplicável à data atual.
Source code in pyield/bday/core.py
def last_business_day() -> dt.date:
    """Retorna o último dia útil no Brasil.

    Se a data atual for um dia útil, retorna a data atual. Se for fim de semana
    ou feriado, retorna o último dia útil antes da data atual.

    Returns:
        O último dia útil no Brasil.

    Notes:
        - A determinação do último dia útil considera a lista de feriados brasileiros
          correta (antes ou depois da transição 2023-12-26) aplicável à data atual.
    """
    # Obtém a data atual do Brasil sem informação de fuso horário
    bz_today = clock.today()
    result = offset(bz_today, 0, roll="backward")
    assert isinstance(result, dt.date), (
        "Premissa violada: offset não retornou uma data para a data atual."
    )
    return result

offset(dates, offset, roll='forward')

Desloca data(s) por um número de dias úteis com regime de feriados brasileiro.

A operação é realizada em duas etapas por elemento: 1) ROLL: Se a data original cair em fim de semana ou feriado, move-a de acordo com roll ("forward" -> próximo dia útil; "backward" -> anterior). 2) ADD: Aplica o offset de dias úteis com sinal (positivo avança, negativo retrocede, zero = permanece na data após roll).

PRESERVAÇÃO DE ORDEM (crítico): A ordenação de saída corresponde estritamente ao pareamento elemento a elemento após expansão entre dates e offset. Nenhuma ordenação, deduplicação ou mudança de forma ocorre. O i-ésimo resultado corresponde ao i-ésimo par (date, offset), permitindo atribuição segura de volta ao DataFrame de origem.

Regime de feriados: Para CADA data, a lista de feriados apropriada (antiga vs. nova) é escolhida com base na data de transição 2023-12-26 (DATA_TRANSICAO). Datas antes da transição usam a lista antiga; datas na transição ou após usam a lista nova.

Semântica do roll: roll só atua quando a data original não é um dia útil sob seu regime. Após o roll, a adição de dias úteis subsequente é aplicada a partir dessa âncora. Um offset de 0 portanto retorna ou a data original (se já for dia útil) ou o dia útil após roll.

Propagação de nulos: Se qualquer argumento escalar for nulo, a função faz curto-circuito para None. Nulos dentro de arrays de entrada propagam para suas posições correspondentes na saída.

Expansão: dates e offset podem ser escalares ou array-like. Regras padrão de expansão do Polars aplicam-se ao construir os pares por linha.

Tipo de retorno: Se ambas as entradas forem escalares não-nulos, um datetime.date é retornado. Caso contrário, uma polars.Series de datas nomeada 'adjusted_date' é produzida. Entradas escalares nulas resultam em None.

Parameters:

Name Type Description Default
dates DateLike | ArrayLike | None

Data única ou coleção de datas a serem ajustadas (roll, se necessário) e então deslocadas. Cada data seleciona independentemente o regime de feriados.

required
offset int | ArrayLike | None

Contagem com sinal de dias úteis a aplicar após o roll. Positivo move para frente, negativo para trás, zero mantém a âncora após roll.

required
roll Literal['forward', 'backward']

Direção para ajustar uma data inicial não-útil ("forward" ou "backward"). Padrão é "forward".

'forward'

Returns:

Type Description
date | Series | None

Um date Python para entradas escalares, uma Series Polars de datas para

date | Series | None

qualquer entrada de array, ou None se um argumento escalar nulo foi

date | Series | None

fornecido.

Notes
  • Encapsulamento de polars.Expr.dt.add_business_days aplicado condicionalmente.
  • O regime de feriados é decidido por elemento comparando com DATA_TRANSICAO.
  • Fins de semana são sempre tratados como não-úteis.
  • Strings de data aceitas: DD-MM-YYYY, DD/MM/YYYY e YYYY-MM-DD.
  • Strings inválidas são tratadas como null e propagadas ao resultado.

Examples:

>>> from pyield import bday

Desloca sábado antes do Natal para o próximo dia útil (terça após Natal):

>>> bday.offset("23-12-2023", 0)
datetime.date(2023, 12, 26)

Desloca sexta antes do Natal (sem deslocamento pois é dia útil):

>>> bday.offset("22-12-2023", 0)
datetime.date(2023, 12, 22)

Desloca para o dia útil anterior se não for útil (offset=0 e roll="backward"):

Sem deslocamento pois é dia útil:

>>> bday.offset("22-12-2023", 0, roll="backward")
datetime.date(2023, 12, 22)

Desloca para o primeiro dia útil antes de "23-12-2023":

>>> bday.offset("23-12-2023", 0, roll="backward")
datetime.date(2023, 12, 22)

Avança para o próximo dia útil (offset=1 e roll="forward"):

Desloca sexta para o próximo dia útil (sexta é pulada -> segunda):

>>> bday.offset("27-09-2024", 1)
datetime.date(2024, 9, 30)

Desloca sábado para o próximo dia útil (segunda é pulada -> terça):

>>> bday.offset("28-09-2024", 1)
datetime.date(2024, 10, 1)

Volta para o dia útil anterior (offset=-1 e roll="backward"):

Desloca sexta para o dia útil anterior (sexta é pulada -> quinta):

>>> bday.offset("27-09-2024", -1, roll="backward")
datetime.date(2024, 9, 26)

Desloca sábado para o dia útil anterior (sexta é pulada -> quinta):

>>> bday.offset("28-09-2024", -1, roll="backward")
datetime.date(2024, 9, 26)

Lista de datas e offsets:

>>> bday.offset(["19-09-2024", "20-09-2024"], 1)
shape: (2,)
Series: 'adjusted_date' [date]
[
    2024-09-20
    2024-09-23
]
>>> bday.offset("19-09-2024", [1, 2])  # lista de offsets
shape: (2,)
Series: 'adjusted_date' [date]
[
    2024-09-20
    2024-09-23
]

Nulos escalares propagam para None:

>>> print(bday.offset(None, 1))
None

Nulo escalar propaga dentro de arrays:

>>> bday.offset(None, [1, 2])
shape: (2,)
Series: 'adjusted_date' [date]
[
    null
    null
]

Nulos dentro de arrays são preservados:

>>> bday.offset(["19-09-2024", None], 1)
shape: (2,)
Series: 'adjusted_date' [date]
[
    2024-09-20
    null
]
>>> datas = ["19-09-2024", "20-09-2024", "21-09-2024"]
>>> bday.offset(datas, 1)
shape: (3,)
Series: 'adjusted_date' [date]
[
    2024-09-20
    2024-09-23
    2024-09-24
]
Source code in pyield/bday/core.py
def offset(
    dates: DateLike | ArrayLike | None,
    offset: int | ArrayLike | None,
    roll: Literal["forward", "backward"] = "forward",
) -> dt.date | pl.Series | None:
    """Desloca data(s) por um número de dias úteis com regime de feriados brasileiro.

    A operação é realizada em duas etapas por elemento:
    1) ROLL: Se a data original cair em fim de semana ou feriado, move-a de acordo
       com ``roll`` ("forward" -> próximo dia útil; "backward" -> anterior).
    2) ADD: Aplica o ``offset`` de dias úteis com sinal (positivo avança, negativo
       retrocede, zero = permanece na data após roll).

    PRESERVAÇÃO DE ORDEM (crítico): A ordenação de saída corresponde estritamente
    ao pareamento elemento a elemento após expansão entre ``dates`` e ``offset``.
    Nenhuma ordenação, deduplicação ou mudança de forma ocorre. O i-ésimo resultado
    corresponde ao i-ésimo par (date, offset), permitindo atribuição segura de volta
    ao DataFrame de origem.

    Regime de feriados: Para CADA data, a lista de feriados apropriada (antiga vs.
    nova) é escolhida com base na data de transição 2023-12-26 (``DATA_TRANSICAO``).
    Datas antes da transição usam a lista *antiga*; datas na transição ou após
    usam a lista *nova*.

    Semântica do roll: ``roll`` só atua quando a data original não é um dia útil
    sob seu regime. Após o roll, a adição de dias úteis subsequente é aplicada a
    partir dessa âncora. Um ``offset`` de 0 portanto retorna ou a data original
    (se já for dia útil) ou o dia útil após roll.

    Propagação de nulos: Se qualquer argumento escalar for nulo, a função faz
    curto-circuito para ``None``. Nulos dentro de arrays de entrada propagam para
    suas posições correspondentes na saída.

    Expansão: ``dates`` e ``offset`` podem ser escalares ou array-like. Regras
    padrão de expansão do Polars aplicam-se ao construir os pares por linha.

    Tipo de retorno: Se ambas as entradas forem escalares não-nulos, um
    ``datetime.date`` é retornado. Caso contrário, uma ``polars.Series`` de datas
    nomeada ``'adjusted_date'`` é produzida. Entradas escalares nulas resultam
    em ``None``.

    Args:
        dates: Data única ou coleção de datas a serem ajustadas (roll, se necessário)
            e então deslocadas. Cada data seleciona independentemente o regime de
            feriados.
        offset: Contagem com sinal de dias úteis a aplicar após o roll. Positivo
            move para frente, negativo para trás, zero mantém a âncora após roll.
        roll: Direção para ajustar uma data inicial não-útil ("forward" ou
            "backward"). Padrão é "forward".

    Returns:
        Um ``date`` Python para entradas escalares, uma Series Polars de datas para
        qualquer entrada de array, ou ``None`` se um argumento escalar nulo foi
        fornecido.

    Notes:
        - Encapsulamento de ``polars.Expr.dt.add_business_days`` aplicado
          condicionalmente.
        - O regime de feriados é decidido por elemento comparando com
          ``DATA_TRANSICAO``.
        - Fins de semana são sempre tratados como não-úteis.
        - Strings de data aceitas: ``DD-MM-YYYY``, ``DD/MM/YYYY`` e ``YYYY-MM-DD``.
        - Strings inválidas são tratadas como ``null`` e propagadas ao resultado.

    Examples:
        >>> from pyield import bday

        Desloca sábado antes do Natal para o próximo dia útil (terça após Natal):
        >>> bday.offset("23-12-2023", 0)
        datetime.date(2023, 12, 26)

        Desloca sexta antes do Natal (sem deslocamento pois é dia útil):
        >>> bday.offset("22-12-2023", 0)
        datetime.date(2023, 12, 22)

        Desloca para o dia útil anterior se não for útil (offset=0 e roll="backward"):

        Sem deslocamento pois é dia útil:
        >>> bday.offset("22-12-2023", 0, roll="backward")
        datetime.date(2023, 12, 22)

        Desloca para o primeiro dia útil antes de "23-12-2023":
        >>> bday.offset("23-12-2023", 0, roll="backward")
        datetime.date(2023, 12, 22)

        Avança para o próximo dia útil (offset=1 e roll="forward"):

        Desloca sexta para o próximo dia útil (sexta é pulada -> segunda):
        >>> bday.offset("27-09-2024", 1)
        datetime.date(2024, 9, 30)

        Desloca sábado para o próximo dia útil (segunda é pulada -> terça):
        >>> bday.offset("28-09-2024", 1)
        datetime.date(2024, 10, 1)

        Volta para o dia útil anterior (offset=-1 e roll="backward"):

        Desloca sexta para o dia útil anterior (sexta é pulada -> quinta):
        >>> bday.offset("27-09-2024", -1, roll="backward")
        datetime.date(2024, 9, 26)

        Desloca sábado para o dia útil anterior (sexta é pulada -> quinta):
        >>> bday.offset("28-09-2024", -1, roll="backward")
        datetime.date(2024, 9, 26)

        Lista de datas e offsets:
        >>> bday.offset(["19-09-2024", "20-09-2024"], 1)
        shape: (2,)
        Series: 'adjusted_date' [date]
        [
            2024-09-20
            2024-09-23
        ]

        >>> bday.offset("19-09-2024", [1, 2])  # lista de offsets
        shape: (2,)
        Series: 'adjusted_date' [date]
        [
            2024-09-20
            2024-09-23
        ]

        Nulos escalares propagam para None:
        >>> print(bday.offset(None, 1))
        None

        Nulo escalar propaga dentro de arrays:
        >>> bday.offset(None, [1, 2])
        shape: (2,)
        Series: 'adjusted_date' [date]
        [
            null
            null
        ]

        Nulos dentro de arrays são preservados:
        >>> bday.offset(["19-09-2024", None], 1)
        shape: (2,)
        Series: 'adjusted_date' [date]
        [
            2024-09-20
            null
        ]

        >>> datas = ["19-09-2024", "20-09-2024", "21-09-2024"]
        >>> bday.offset(datas, 1)
        shape: (3,)
        Series: 'adjusted_date' [date]
        [
            2024-09-20
            2024-09-23
            2024-09-24
        ]
    """
    s = (
        pl.DataFrame(
            data={"dates": dates, "offset": offset},
            nan_to_null=True,
        )
        .select(adjusted_date=offset_expr("dates", n="offset", roll=roll))
        .get_column("adjusted_date")
    )

    if not tp.any_is_collection(dates, offset):
        return s.item()

    return s

offset_expr(expr, n, roll='forward')

Cria uma expressão Polars para somar dias úteis.

Ideal para operações vetorizadas em DataFrames ou LazyFrames.

Parameters:

Name Type Description Default
expr Expr | str

Coluna de data original.

required
n int | Expr | str

Número de dias úteis a somar. Pode ser um inteiro fixo ou outra coluna.

required
roll Literal['forward', 'backward']

Como tratar a data inicial se ela cair em fim de semana/feriado.

'forward'

Returns:

Type Description
Expr

Uma pl.Expr que resulta em Date.

Examples:

>>> import datetime as dt
>>> import polars as pl
>>> from pyield.bday import offset_expr
>>> datas = [dt.date(2023, 12, 22), dt.date(2023, 12, 29)]
>>> offsets = [1, 5]
>>> df = pl.DataFrame({"dt": datas, "n": offsets})

Adicionando um valor fixo (1 dia útil):

>>> df.select(offset_expr("dt", 1).alias("t_plus_1"))
shape: (2, 1)
┌────────────┐
│ t_plus_1   │
│ ---        │
│ date       │
╞════════════╡
│ 2023-12-26 │
│ 2024-01-02 │
└────────────┘

Adicionando uma coluna dinâmica (prazo variável por linha):

>>> df.select(offset_expr("dt", "n").alias("vencimento"))
shape: (2, 1)
┌────────────┐
│ vencimento │
│ ---        │
│ date       │
╞════════════╡
│ 2023-12-26 │
│ 2024-01-08 │
└────────────┘
Source code in pyield/bday/core.py
def offset_expr(
    expr: pl.Expr | str,
    n: int | pl.Expr | str,
    roll: Literal["forward", "backward"] = "forward",
) -> pl.Expr:
    """Cria uma expressão Polars para somar dias úteis.

    Ideal para operações vetorizadas em DataFrames ou LazyFrames.

    Args:
        expr: Coluna de data original.
        n: Número de dias úteis a somar. Pode ser um inteiro fixo ou outra coluna.
        roll: Como tratar a data inicial se ela cair em fim de semana/feriado.

    Returns:
        Uma ``pl.Expr`` que resulta em Date.

    Examples:
        >>> import datetime as dt
        >>> import polars as pl
        >>> from pyield.bday import offset_expr
        >>> datas = [dt.date(2023, 12, 22), dt.date(2023, 12, 29)]
        >>> offsets = [1, 5]
        >>> df = pl.DataFrame({"dt": datas, "n": offsets})

        Adicionando um valor fixo (1 dia útil):
        >>> df.select(offset_expr("dt", 1).alias("t_plus_1"))
        shape: (2, 1)
        ┌────────────┐
        │ t_plus_1   │
        │ ---        │
        │ date       │
        ╞════════════╡
        │ 2023-12-26 │
        │ 2024-01-02 │
        └────────────┘

        Adicionando uma coluna dinâmica (prazo variável por linha):
        >>> df.select(offset_expr("dt", "n").alias("vencimento"))
        shape: (2, 1)
        ┌────────────┐
        │ vencimento │
        │ ---        │
        │ date       │
        ╞════════════╡
        │ 2023-12-26 │
        │ 2024-01-08 │
        └────────────┘
    """
    if isinstance(expr, str):
        expr = pl.col(expr)
    if isinstance(n, str):
        n = pl.col(n)

    expr_date = cv.converter_datas_expr(expr)

    return expr_date.dt.add_business_days(
        n=n,
        roll=roll,
        holidays=_expressao_feriados(expr_date),
    )