2 puntos por GN⁺ 2024-07-23 | 1 comentarios | Compartir por WhatsApp
  • Jiff es una biblioteca de fecha y hora para Rust, cuyo objetivo es ofrecer tipos básicos de fecha y hora de alto nivel difíciles de usar mal, junto con un rendimiento razonable
  • Sus funciones principales son la integración automática con la Time Zone Database, aritmética y redondeo conscientes del DST, formateo y parseo sin pérdida de fechas y horas con zona horaria, y soporte opcional para Serde
  • El diseño de la API recibió una fuerte influencia de Temporal, la propuesta de TC39 para mejorar el manejo de fechas y horas en JavaScript
  • Jiff 1.0 originalmente tenía como objetivo lanzarse en el verano de 2025, pero para abril de 2026 el calendario ya se había retrasado y actualmente no hay una línea de tiempo para el lanzamiento 1.0
  • El rendimiento es un objetivo importante, pero se priorizan la comprensibilidad de la API y la corrección; en Unix sigue una política conservadora de dependencias que mantiene intencionalmente 0 dependencias

El modelo de fecha y hora que ofrece Jiff

  • Jiff es una biblioteca para manejar fechas y horas en Rust, enfocada en proporcionar tipos básicos de alto nivel difíciles de usar incorrectamente
  • Funciones principales compatibles
    • Integración automática y fluida con la Time Zone Database
    • Aritmética y redondeo conscientes del DST
    • Formateo y parseo sin pérdida de fechas y horas con zona horaria
    • Soporte opcional para serde
  • El diseño de la API recibió una fuerte influencia de Temporal, la propuesta de TC39 para mejorar el manejo de fechas y horas en JavaScript
  • La licencia es doble: MIT o UNLICENSE

Documentación y ejemplos de uso

  • Documentación y materiales relacionados
    • Documentación de la API en docs.rs
    • Documentos comparativos con chrono, time, hifitime e icu
    • Documento de justificación del diseño de la API de Jiff
    • Documento de soporte de plataformas
    • Registro de cambios
  • El ejemplo básico parsea una cadena de tiempo RFC 3339, la convierte a una fecha y hora con zona horaria, suma una duración y luego la imprime como una cadena sin pérdida
    • Parsea "2024-07-11T01:14:00Z" como Timestamp
    • La convierte a la zona horaria America/New_York
    • Suma 1.month().hours(2)
    • La cadena resultante es "2024-08-10T23:14:00-04:00[America/New_York]"
    • La hora UTC en formato RFC 3339 es "2024-08-11T03:14:00Z"

Instalación y flujo básico de ejecución

  • Jiff está en crates.io y puede usarse agregando jiff a las dependencias de Cargo.toml en un proyecto Rust
  • El comando simple para agregarlo es cargo add jiff
  • Flujo completo del ejemplo en el README
    • Crear un proyecto nuevo con cargo new jiff-example
    • Agregar la dependencia con cargo add jiff
    • En src/main.rs, redondear la hora actual a segundos con Zoned::now().round(Unit::Second)?
    • Ejecutar con cargo run
  • La salida de ejemplo muestra una forma que incluye zona horaria: 2024-07-10T19:54:20-04:00[America/New_York]
  • En la primera ejecución aparece más salida de compilación, pero en ejecuciones posteriores no hace falta recompilar las dependencias

Feature flags y plan para 1.0

  • Jiff ofrece varias features de crate para ajustar el soporte de la biblioteca estándar de Rust, el soporte para serde y si se incrusta o no la Time Zone Database en el binario
  • La lista completa de feature flags compatibles está en la sección “crate features” de la documentación
  • El plan original de lanzamiento de Jiff 1.0 era el verano de 2025, pero para abril de 2026 el calendario ya se había retrasado y actualmente no hay una línea de tiempo de lanzamiento
  • Después del lanzamiento 1.0, el plan es mantener la API indefinidamente; el objetivo es que los usuarios puedan usar Jiff como una base estable de API pública
  • Incluso después del lanzamiento de Jiff 1.0, Jiff 0.2 tiene previsto recibir actualizaciones de corrección de errores importantes durante 1 año
    • No habrá desarrollo activo de funciones
    • Como ya hay muchos usuarios usando Jiff 0.2, el plan es ofrecer un período de transición

Rendimiento y rutas avanzadas de formateo

  • El objetivo de diseño más importante es ser una biblioteca de fecha y hora de alto nivel difícil de usar mal
  • El rendimiento es el segundo objetivo; apunta a un rendimiento razonable, aunque puede haber áreas con margen de mejora
  • Los benchmarks están en el directorio bench
  • Algunas áreas ya fueron optimizadas, pero todavía hay muchas áreas sin optimizar
  • Mejorar el rendimiento es un objetivo, pero en general la comprensibilidad de la API y la corrección tienen prioridad sobre el rendimiento
  • Los submódulos de Jiff, especialmente jiff::fmt, funcionan como una vía de escape hacia APIs más complejas cuando se necesita más control

Manejo de zonas horarias por plataforma

  • Los problemas de soporte de plataformas en una biblioteca de fecha y hora están relacionados principalmente con el soporte de zonas horarias
  • Interacciones con la plataforma que maneja Jiff
    • Cómo encontrar los datos de transiciones de identificadores de zona horaria IANA como Antarctica/Troll
    • Cómo encontrar la zona horaria predeterminada del sistema actual
  • Los datos de transiciones de zonas horarias dependen de la IANA Time Zone Database
  • En Unix, normalmente se usa /usr/share/zoneinfo y también se respeta la variable de entorno TZDIR
  • En Windows, se incrusta automáticamente una copia de la Time Zone Database en la biblioteca compilada
  • Métodos de detección de la zona horaria del sistema
    • En Unix, lee /etc/localtime
    • En Windows, lee el identificador de zona horaria específico de Windows mediante GetDynamicTimeZoneInformation y lo mapea a un identificador de zona horaria IANA mediante los datos XML CLDR de Unicode
  • Se espera que el soporte de otras plataformas aumente con el tiempo, pero las plataformas poco comunes y difíciles de probar probablemente dependan de pull requests de colaboradores

Dependencias y política de MSRV

  • Que Jiff tenga 0 dependencias en Unix es una decisión de diseño intencional
  • El criterio para agregar una nueva dependencia a Jiff, una crate del ecosistema, es conservador
    • Cuando sea realmente necesaria para interactuar con la plataforma
    • Cuando sea necesaria para interoperabilidad, por ejemplo serde
  • Incluso las dependencias con fines de interoperabilidad se tratan de forma conservadora y, en general, se busca depender solo de aquellas que tengan menos lanzamientos con breaking changes que Jiff
  • Dividir Jiff en varias crates podría generar nuevas dependencias, pero actualmente no hay planes para hacerlo
  • La orientación es mantener bajo el número de crates, ya que la experiencia muestra que gestionar muchos límites de API semver implica un alto costo de mantenimiento
  • La versión mínima soportada del compilador de Rust para Jiff es MSRV 1.70.0
  • Jiff en sí y las dependencias de Jiff dentro del repositorio no usan versiones de Rust que requieran un compilador de Rust lanzado durante el último año
  • Las crates dentro del repositorio que dependen de Jiff, como jiff-icu, pueden usar una MSRV más reciente, pero no exigirán una MSRV más nueva que la crate con la que se integran
  • Jiff no controla la MSRV de dependencias externas como windows-sys y portable-atomic
  • Según la convención del ecosistema Rust, Jiff no considera un aumento de MSRV como un cambio incompatible con semver, pero limita los aumentos de MSRV a actualizaciones de minor version

1 comentarios

 
GN⁺ 2024-07-23
Comentarios en Hacker News
  • En general se ve bien, pero me hizo ruido la sintaxis de ToSpan
    let span = 5.days().hours(8).minutes(1);
    Se siente raro que solo el primer número vaya al frente y que el resto entre como argumentos de función
    Si no te gusta, también se puede escribir como let span = Span::new().days(5).hours(8).minutes(1);, y como solo añade unas cuantas letras, tampoco está mal

    • use jiff::{Timestamp, ToSpan}; y luego "2024-07-11T01:14:00Z".parse()?: ahí me chocó la parte en la que se parsea como Timestamp
      Según recuerdo, Rust usa algo más parecido a interfaces que a clases, pero me intriga por qué al importar una librería de repente a un string le aparece un método parse(), y por qué ese método con un nombre tan genérico termina creando un objeto Timestamp
      Me pregunto si el tipo de la izquierda es lo que determina el significado de str.parse(), y también qué pasa si tanto una librería de fechas como una librería de expresiones Lisp le agregan parse() a los strings
      Eso incluso me lleva a preguntar por qué no usan una sintaxis como Timestamp.parse(str)
    • De acuerdo. Personalmente prefiero let span = 5.days() + 8.hours() + 1.minutes();
    • Me gusta esta versión porque se ve consistente
      La sintaxis original parece algo que haría Ruby
    • De acuerdo. En mi flujo mental, primero quiero especificar qué estoy haciendo y luego añadir los detalles, pero esta sintaxis lo invierte
      Al leer el código, preferiría ver primero que se está trabajando con un span
    • Yendo más allá, let span = Span::days(5).hours(8).minutes(1); se ve todavía mejor
  • He visto muchas veces que se subestima la complejidad de las librerías de fecha/hora
    Se repiten cosas como “la representación interna puede ser UTC/Unix time”, “los períodos pueden representarse en nanosegundos” o “en vez de zonas horarias basta con offsets”
    Si piensas así, vale la pena leer el documento de diseño de Jiff: https://github.com/BurntSushi/jiff/blob/master/DESIGN.md
    Como es trabajo de BurntSushi, es excelente y muy detallado
    También vale la pena leer la comparación con chrono, que es la librería de fecha/hora estándar de facto en Rust: https://docs.rs/jiff/latest/jiff/_documentation/comparison/i...
    Cosas como la aritmética correcta con DST incluso después de serialización/deserialización, los períodos redondeables, la aritmética de calendario con reconocimiento de zona horaria y la detección de conflictos históricos de zonas horarias son las que hacen que una librería sea precisa, útil y agradable de usar
    chrono es una librería amplia y “correcta”, pero era rígida y no muy fácil de usar

    • Me gusta ripgrep de BurntSushi y lo invoco directo desde Emacs, así que lo uso todo el tiempo
      Ya usaba ripgrep desde antes de que Debian incluyera rg por defecto, y también usaba JodaTime en la época en que había gente que pensaba que Eclipse era mejor que IntelliJ IDEA
      Pero en ninguna parte de esa documentación se rebate la idea de que “los períodos pueden representarse en nanosegundos”
      El usuario necesita ver la hora correcta según la zona horaria y el DST, pero el programa por lo general no
      A menos que el trabajo implique manejar directamente varias zonas horarias, representar los períodos en milisegundos/nanosegundos suele ser muy seguro
      Que los humanos hayan inventado las zonas horarias y el DST no cambia el hecho físico de que el reloj interno de la CPU hace tic decenas de miles de millones de veces por segundo
      Basta ver kernels de sistemas operativos que no se rompieron hace unos días en media Tierra: en muchos lugares el código expresa los timeouts en milisegundos
      Esto puede dar la impresión de que, para implementar un “cooldown de 30 segundos”, hay que sumarle 30 segundos a la hora actual en la zona horaria vigente, guardarlo como string incluyendo zona horaria, DST y formato de 12/24 horas, y hasta revisar si hay segundos intercalares
      Simplemente puedes usar un timeout/cooldown de 30 segundos expresado en milisegundos, y aunque para el usuario ocurra 29 segundos después, no hace falta preocuparse por los segundos intercalares
    • Fue hasta que vi “funciona incluso después de ser/de” que entendí por qué el crate Serde se llama así
    • Gracias por compartir el documento de diseño. Lo pasé por alto la primera vez que revisé el repositorio, pero está muy bien escrito y me aclaró muchas dudas que tenía
    • Si quieres ver la locura de fecha/hora de una forma entretenida y accesible, la charla de Kip Cole en ElixirConf 2022 está muy buena: https://www.youtube.com/watch?v=4VfPvCI901c
    • A quien crea que las fechas/horas son fáciles no hay que dejarlo cerca del código de scheduling ni del código de manejo de fechas
  • Parece una gran librería.
    Me pregunto por qué BurntSushi está creando una librería nueva.
    No he trabajado mucho con manejo de tiempo en Rust, pero me gustaría saber si las librerías existentes tienen problemas de rendimiento, si las APIs actuales son incómodas de usar, si lo hace solo por diversión o si hay alguna otra razón.

  • El estado de las librerías de calendario en Rust no es precisamente ideal.
    En Pandas tienes .tz_convert() y .tz_localize(), y la conversión de zona horaria prácticamente se resuelve con eso.
    La tarea que uso como referencia es “obtener en UTC la primera hora del día CET/CEST para una fecha dada”, y en Pandas es muy simple.
    En Chrono hay que crear un NaiveDate, convertirlo a DateTime y luego volver a convertirlo a DateTime, y no he encontrado un patrón consistente para ese tipo de conversiones.
    A veces es una función miembro y otras veces es un método estático del objeto de zona horaria.
    Ojalá alguien lo arregle algún día. Jiff parece un paso en la dirección correcta, pero su sintaxis a veces se siente rara; me gustaría algo más predecible.

    • Eso se puede hacer con date(y, m, d).intz("Europe/Rome")?.
      Si de ahí quieres obtener UTC, puedes agregar with_time_zone(TimeZone::UTC).
      Me gustaría que explicaras un poco más cómo podría mejorar Jiff y qué parte de la sintaxis te parece rara.
  • “Cariño, despierta, salió un nuevo BurntSushi”[0]
    Hablando en serio, me pregunto si alguien aquí que use Rust sabe por qué este crate no usa algo como tracing.
    Puede que sea porque es demasiado pesado. En tamaño del crate, casi lo duplica.
    Claro que log también está bien, y tampoco sé si rastrear hasta las llamadas de operaciones de zona horaria sea un caso de uso común, pero me da curiosidad si hubo una razón en particular.
    [0] https://knowyourmeme.com/memes/wake-up-babe

    • Diría que log es algo así como el mínimo común denominador.
      Además se integra bien con tracing, así que probablemente Jiff lo usa por eso.
      También es algo que lleva usando desde los primeros días de crates.io.
      Jiff no tiene más requisitos que emitir mensajes en ciertos niveles de registro.
      De todos modos, creo que tracing también habría funcionado bien.
  • BurntSushi también es la persona que creó todo el ecosistema de expresiones regulares de Rust.

    • Recuerdo haber discutido con alguien en Reddit sobre algo, darme cuenta de quién era la otra persona y decir “olvídalo”.
  • Tanto en Rust como en otros ecosistemas, lo que más echo de menos en las librerías de tiempo existentes es la falta de soporte para segundos intercalares.
    Por lo general usan internamente timestamps UNIX en vez de TAI, y por desgracia esta librería no es diferente.
    Parece haber alguna forma de darles cierto soporte mediante archivos tzif, pero no es soporte de primera clase.
    Hay más detalles en este issue de Jiff: https://github.com/BurntSushi/jiff/issues/7
    Como los timestamps UNIX no usan la definición de segundo del SI sino el 1/86000 del día actual, no todos los segundos UNIX tienen la misma duración y eso rompe los cálculos correctos de intervalos.
    Entiendo que fue una concesión para heredar compatibilidad con formas antiguas de seguimiento del tiempo y para formatear calendarios rápido, pero no me parece que haya sido la concesión correcta.
    Los timestamps UNIX mezclan preocupaciones de representación con los datos.
    Creo que los segundos intercalares deberían tratarse exactamente igual que el 29 de febrero o las zonas horarias.

    • Me da curiosidad por qué sería necesario. Quisiera saber cuál es el caso de uso real, y también por qué una librería científica especializada como hifitime no serviría.
      Cada vez que sale el tema de los segundos intercalares, casi siempre parece derivar en una conversación abstracta.
      Muy rara vez se conecta con casos de uso del mundo real.
      Entiendo los casos de uso científicos, y me parece que para eso las librerías especializadas lo resuelven bien.
      Fuera de eso, no estoy seguro de qué más haría falta.
  • Jiff se pronuncia con G dura, o sea “Giff”.

    • No soy hablante nativo, pero ¿qué significa exactamente hard G? ¿Es la G de go o la G de gin?
  • “Jiff se pronuncia gif con una g suave, como en gem.”
    ¡De ninguna manera!

    • Tuvo un buen rato en el puesto número 1 de Hacker News, pero creo que ahora se ha revelado un defecto fatal.
      Yo me quedo con chrono †
      † claro, pronunciado “trono”, como la ch de chase
    • https://x.com/drgonzo123/status/527520297512345600
    • Qué raro, yo pensé que Jiff se pronunciaba con una “J” dura.
    • ¿Se pronuncia GIF y no GIF?
  • Entonces, ¿se pronuncia jiff o gif?

    • https://github.com/BurntSushi/jiff/blob/master/DESIGN.md#why...
      “Jiff se pronuncia gif con una g suave, como en gem.”
      Ah, ya entendí el chiste...
    • Creo que la pronunciación la decide quien lo creó.
      Según su creador, GIF siempre se pronunció “jif”, así que para evitar confusiones estaría bien que el autor de esta nueva herramienta pronunciara Jiff como “gif”.
    • Como es una librería para manejar tiempo, yo simplemente le voy a decir biff