python / python/cpython

time/datetime.fromisoformat: C accelerator accepts a fractional seconds component with no decimal mark

Abierto
#155,175 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

extension-modules type-bug
Lenguaje dominante
Python
Estrellas
77.2k
Forks
35.9k
Métricas de merge de PR
Métricas de PR pendientes

Descripción

Bug report

Bug description:

In the basic ISO 8601 time format, the C implementation of
time.fromisoformat() / datetime.fromisoformat() accepts digits following
HHMMSS and silently interprets them as a fractional seconds component, even
though no decimal mark (. or ,) is present. ISO 8601 requires a decimal
sign to introduce a fraction, and the pure-Python implementation correctly
rejects these strings.

>>> from datetime import time, datetime
>>> time.fromisoformat('12345678')
datetime.time(12, 34, 56, 780000)          # expected ValueError
>>> time.fromisoformat('123456789')
datetime.time(12, 34, 56, 789000)          # expected ValueError
>>> datetime.fromisoformat('2020-01-01T12345678')
datetime.datetime(2020, 1, 1, 12, 34, 56, 780000)   # expected ValueError

The pure-Python implementation rejects all of the above:

>>> import _pydatetime
>>> _pydatetime.time.fromisoformat('12345678')
Traceback (most recent call last):
  ...
ValueError: Invalid isoformat string: '12345678'

The same parser is used for the UTC offset, so a malformed offset is accepted
too, with the extra digits silently discarded:

>>> time.fromisoformat('12:34:56+00000000')
datetime.time(12, 34, 56, tzinfo=datetime.timezone.utc)   # expected ValueError

Two further details show the leniency is unintended rather than deliberate:

  • The equivalent extended-format string is correctly rejected —
    time.fromisoformat('12:34:5678') raises ValueError.
  • The behaviour is not even self-consistent across lengths. '1234567'
    (7 digits) raises ValueError, while '12345678' (8 digits) is accepted.

This matters because the failure is silent and produces a wrong value rather
than an error: a truncated or corrupted timestamp string parses successfully
into a time/datetime that differs from the input.

Cause

In parse_hh_mm_ss_ff() (Modules/_datetimemodule.c), the HH/MM/SS loop
breaks when it sees a decimal mark, but in the basic format it can also fall
out of the loop normally after SS with characters still unconsumed (the
!has_separator branch does --p). The code following the loop then parses
whatever remains as a fraction, without ever checking that a decimal mark
introduced it.

The pure-Python _parse_hh_mm_ss_ff() in Lib/_pydatetime.py performs that
check explicitly:

if pos < len_str:
    if tstr[pos] not in '.,':
        raise ValueError("Invalid microsecond separator")
Documentation

fromisoformat() is documented to accept "any valid ISO 8601 format" subject to
an explicit list of exceptions; a fraction without a decimal sign is not among
them.

This is the same class of C/pure-Python divergence as gh-152157 (empty fraction
before a timezone designator) and gh-152079.

Affected versions

Reproduced directly on 3.12.3 and on main (3.16.0a0). I did not have 3.13,
3.14 or 3.15 interpreters to hand, but comparing parse_hh_mm_ss_ff() across
the branches shows the cause is present in all of them: the
else if (!has_separator) { --p; } branch and the unguarded "Parse fractional
components" block that follows the loop are unchanged on every active branch.
The 3.14, 3.15 and main copies of the function are identical; the 3.12 and
3.13 copies differ only in other validation paths (decimal mark on
hour/minute, empty fraction, microsecond separator).

CPython versions tested on:

3.12, 3.16, CPython main branch

Operating systems tested on:

Linux

Linked PRs
  • gh-155177

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Línea de trabajo

Comienza con parse_hh_mm_ss_ff() en Modules/_datetimemodule.c y compara su validación con _parse_hh_mm_ss_ff() en Lib/_pydatetime.py. Reproduce los ejemplos de formato básico y de offsets malformados del informe, y después añade cobertura de regresión para que las entradas sin separador decimal sean rechazadas de forma consistente por la implementación en C.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
c, python
Área
backend
Tipo de issue
Error
Dificultad
2/5
Tiempo estimado
1-3 horas
Estado de actividad
Tranquilo
Claridad
Bien especificado
Aptitud para principiantes
25/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.