Migración de Joda-Time a java.time

Publicado el 12 de noviembre de 2021
por Cameron Gregor
ESTUDIO DE CASO

Migración de Joda-Time a java.time

Publicado el 12 de noviembre de 2021
por Cameron Gregor
Ver recurso
Ver recurso

Migrar código (léase código heredado) no es divertido. Requiere una enorme cantidad de planificación y esfuerzo para llevarlo a cabo. Aunque no es el trabajo más emocionante o motivador para los desarrolladores, se requiere determinación y la experiencia adecuada para migrar el código heredado a las nuevas versiones de las bibliotecas. Joda-Time a java.time es una de esas migraciones que requiere una planificación y ejecución meticulosas.

Si su proyecto Java comenzó su vida antes de Java SE 8, y utiliza el procesamiento de fecha/hora, entonces probablemente utilizó Joda-Time - una excelente biblioteca y un estándar de facto para manejar las funciones de fecha y hora antes de SE 8. Si su proyecto todavía utiliza Joda-Time pero le gustaría migrar a java.time entonces siga leyendo.

El lanzamiento de Java SE 8 incluyó una nueva y mejorada API de fecha y hora estándar comúnmente conocida como java.time (JSR-310). El proyecto Joda-Time recomienda ahora migrar a java.time (JSR-310).

Aunque java.time (JSR-310) se inspiró en gran medida en Joda-Time, no es compatible con versiones anteriores y los conceptos y la terminología han cambiado. Por eso, la migración de Joda-Time a java.time requiere una cuidadosa atención a cada línea de código que se cambie. Esto puede llevar mucho tiempo y casi te haría desear que hubiera una forma más fácil y automatizada de migrar.

Hay una forma mejor de migrar y la hemos creado usando Sensei - un plugin de IntelliJ para realizar automáticamente transformaciones de código según las recetas (reglas) definidas por usted. Dedique su tiempo a definir recetas reutilizables, en lugar de realizar tareas de migración repetitivas. La automatización no sólo transformará su código heredado de Joda-Time, sino que también ayudará a los equipos a seguir las directrices allí mismo, en el IDE, mientras escriben el nuevo código.

Para ayudarte a tener una ventaja, hemos creado un libro de cocina público Sensei Standardization on java.time (JSR-310) que incluye recetas para migrar de Joda-Time a java.time de una manera menos dolorosa. Se trata de un conjunto creciente de recetas que seguiremos ampliando para añadir más cobertura con más recetas.

Este es un ejemplo de una migración de muestra que puede ayudarle a ver cómo Sensei facilita la migración de código heredado.

Vídeo de cómo establecer la hora de la fecha de java.time zoned

De la migración manual repetitiva a las transformaciones de código automatizadas

Veamos un ejemplo de creación de un nuevo DateTime que demuestra unas cuantas trampas ocultas al migrar una sola línea de código de Joda-Time a java.time. A continuación, veremos una de nuestras recetas de Sensei de nuestro libro de cocina Standardization on java.time (JSR-310) y mostraremos cómo captura toda esta información, para que esta misma migración pueda ser reutilizada una y otra vez por cualquier desarrollador.

En este ejemplo, estamos construyendo un Joda-Time DateTime a partir de 7 argumentos int que representan valores de los campos DateTime.

¿Cómo podemos migrar esto a un equivalente de java.time?

El javadoc en Joda-Time para este constructor dice:

Construye una instancia a partir de los valores de los campos datetime utilizando ISOChronology en la zona horaria por defecto.

Al principio, podríamos suponer que hay una clase DateTime en java.time, pero no la hay. Si buscas en Google "migrar de joda time a java time" es muy probable que encuentres la entrada del blog de Stephen Colebourne Converting from Joda-Time to java.time.

Esto le da un buen comienzo, y nos apunta en la dirección de usar java.time.ZonedDateTime o java.time.OffsetDateTime. Aquí está nuestra primera pregunta, ¿cuál utilizo? Probablemente ZonedDateTime basado en los comentarios de Stephen.

Buscando en el javadoc de ZonedDateTime, no vemos ningún constructor. Volviendo a la entrada del blog de Stephen leemos más abajo:

Construcción. Joda-Time tiene un constructor que acepta un objeto y realiza la conversión de tipo. java.time sólo tiene métodos de fábrica, por lo que la conversión es un problema del usuario, aunque se proporciona un método parse() para las cadenas.

Así que debe haber un método estático de fábrica, buscando en los métodos estáticos encontramos uno que se parece bastante, pero no es exactamente igual.

Tiene 7 parámetros int como nuestro constructor Joda-Time DateTime original, sin embargo si no prestas atención te perderás un detalle importante. El 7º parámetro ya no representa milisegundos, en su lugar espera nanosegundos, esto se debe a que java.time ha aumentado la precisión respecto a Joda-Time y mide los instantes al nano-segundo. Un detalle importante que podría haber pasado fácilmente por alto. Además, este método espera un ZoneId, por lo que hace que te preguntes por qué no lo necesitabas antes y por qué lo necesitas ahora.

Recordando el javadoc de nuestro constructor original que mencionaba que usaría la zona horaria por defecto, ¿quizás haya una forma de obtener el ZoneId por defecto?

El javadoc de ZoneId no nos habla de ningún constructor listado, pero mirando los métodos estáticos vemos que podemos usar systemDefault()

Ahora que hemos resuelto el ZoneId, ¿qué debemos hacer con nuestra conversión de milisegundos a nanoSegundos? Tal vez podamos utilizar java.util.concurrent.TimeUnit para realizar la conversión.

Este método devuelve un long, y nuestro método espera un int, así que ahora también tenemos un problema de conversión que resolver. Tal vez podríamos intentar algo sencillo. ¿Una multiplicación?

Esto funcionará pero parece un poco fuera de lugar. Por si no lo has notado ya, hemos invertido una cantidad considerable de tiempo y esfuerzo para migrar una sola línea de código. Pero como puedes imaginar, tenemos muchas ediciones de este tipo que hacer a mano y no mejora.

Sin embargo, si miramos un poco más a la API de java.time, podemos descubrir una solución que parece un poco más fluida.

Aunque ZonedDateTime no tiene una forma obvia de establecer los milisegundos, puede hacerse mediante el método with(TemporalField field, long newValue), utilizando ChronoField.MILLI_OF_SECOND como TemporalField.

Y la documentación de java menciona que realizará la conversión a nanosegundos por nosotros:

Cuando este campo se utiliza para establecer un valor, debe comportarse de la misma manera que establecer NANO_OF_SECOND con el valor multiplicado por 1.000.000.

Así que podemos simplemente especificar 0 para nuestros nanosegundos en el método de fábrica, y luego usar el método with para crear un ZonedDateTime que tenga todos los valores originales así como los milisegundos.

Mirando nuestro resultado final, parece que sólo hemos cambiado una línea de código, ¡no muestra realmente el esfuerzo que ha supuesto investigar una sola migración!

Cree una receta para migrar más rápida y fácilmente

Sensei nos ofrece una forma de compartir con otros desarrolladores esta información que tanto nos ha costado conseguir. Al crear una receta que recoge todos estos requisitos, permitirá a los usuarios de Sensei realizar esta migración con un clic del ratón.

Una receta de Sensei consta de 3 secciones principales:

  • Metadatos
  • Buscar en
  • Arreglos disponibles

Veamos una receta de Sensei (también puede verse como una receta YAML) que nos ayudará a migrar esta llamada a su equivalente java.time.

DateTime foo = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond);

Sección de metadatos

La sección de metadatos contiene información sobre la receta y cómo debe utilizarse.

Sección de búsqueda

La sección de búsqueda de una receta de Sensei especifica a qué elementos de código debe aplicarse esta receta.

search:
instanceCreation:
args:
1:
type: int
2:
type: int
3:
type: int
4:
type: int
5:
type: int
6:
type: int
7:
type: int
argCount: 7
type: org.joda.time.DateTime

En esta sección de búsqueda vemos que estamos:

  • Buscar una instanceCreation, es decir, un uso de un Constructor. Nota: hay muchos otros objetivos de búsqueda disponibles
  • El constructor debe tener 7 argumentos, esto se especifica con la propiedad argCount
  • los argumentos 1-7 deben ser de tipo int
  • Buscamos constructores del tipo org.joda.time.DateTime

Sección de arreglos disponibles

La sección availableFixes puede especificar una o más correcciones que pueden aplicarse al elemento de código correspondiente. Cada fix puede tener múltiples acciones, y en nuestro caso tenemos un único fix, que realiza 2 acciones.

  • El nombre de la corrección se muestra al usuario en el menú "Quickfixes", y describe lo que ocurrirá si el usuario aplica esta quickfix
  • La lista de acciones muestra las acciones que realizará este quickfix
  • La acción de reescritura reescribirá el elemento de código utilizando una plantilla mustache. Puede hacer uso de variables y funciones de sustitución de cadenas.
  • La acción modifyAssignedVariable comprobará si este constructor se está utilizando para asignar el valor a una variable. Si es así, esta acción modificará la variable para que sea declarada como el tipo especificado por type

Uso de la receta para la transformación del código

Con nuestra receta escrita y habilitada, escanea nuestro código y destaca los segmentos a los que se puede aplicar.

En la captura de pantalla de abajo podemos ver que el constructor de destino ha sido marcado por Sensei. Al pasar el ratón por encima del constructor marcado nos muestra la receta shortDescription y la opción Quickfix Migrate to java.time.ZonedDateTime

Migración de la nueva fecha y hora

Después de seleccionar el quickfix Migrate to java.time.ZonedDateTime, el código se transforma según las acciones que especificamos en la receta.

Fecha Hora Zonificada Con Año Mes Día Hora

Una migración única y prácticas de codificación uniformes en todos los equipos, con Sensei

Podemos ver en nuestro ejemplo anterior que la migración de una sola línea de código puede implicar un conocimiento duramente ganado. Sensei puede convertir ese conocimiento en recetas o libros de cocina procesables que pueden ser compartidos dentro de los equipos. Puedes planificar un sprint de migración único o adoptar el enfoque de hacer transformaciones incrementales instantáneas a java.time a medida que te encuentres con código Joda-Time. Puede habilitar/deshabilitar recetas como una forma de hacer migraciones en etapas o pasos lógicos e incluso, ampliar o reducir el alcance de los archivos escaneados por Sensei - la flexibilidad que hace que las migraciones de código sean menos dolorosas.

La migración de librerías es sólo un ejemplo de las muchas maneras en que Sensei puede utilizarse para estandarizar tus proyectos. Siempre puedes estar atento a los antipatrones o a ciertas transformaciones manuales de código que encuentres con frecuencia en pull requests o mientras codificas tú mismo. Si tienes un conjunto de directrices de codificación que los desarrolladores no siguen a menudo, puedes convertir las directrices en recetas, permitiendo a los desarrolladores aplicar transformaciones de código aprobadas con confianza.

Si tienes alguna pregunta, nos encantaría que nos la hicieras llegar. Únase a nosotros en Slack en: sensei-scw.slack.com

Ver recurso
Ver recurso

Autor

Cameron Gregor

¿Quieres más?

Sumérjase en nuestras últimas ideas sobre codificación segura en el blog.

Nuestra amplia biblioteca de recursos tiene como objetivo potenciar el enfoque humano de la mejora de la codificación segura.

Ver blog
¿Quieres más?

Obtenga las últimas investigaciones sobre la seguridad impulsada por los desarrolladores

Nuestra amplia biblioteca de recursos está repleta de recursos útiles, desde libros blancos hasta seminarios web, que le ayudarán a iniciarse en la codificación segura orientada a los desarrolladores. Explórela ahora.

Centro de recursos

Migración de Joda-Time a java.time

Publicado el 12 de noviembre de 2021
Por Cameron Gregor

Migrar código (léase código heredado) no es divertido. Requiere una enorme cantidad de planificación y esfuerzo para llevarlo a cabo. Aunque no es el trabajo más emocionante o motivador para los desarrolladores, se requiere determinación y la experiencia adecuada para migrar el código heredado a las nuevas versiones de las bibliotecas. Joda-Time a java.time es una de esas migraciones que requiere una planificación y ejecución meticulosas.

Si su proyecto Java comenzó su vida antes de Java SE 8, y utiliza el procesamiento de fecha/hora, entonces probablemente utilizó Joda-Time - una excelente biblioteca y un estándar de facto para manejar las funciones de fecha y hora antes de SE 8. Si su proyecto todavía utiliza Joda-Time pero le gustaría migrar a java.time entonces siga leyendo.

El lanzamiento de Java SE 8 incluyó una nueva y mejorada API de fecha y hora estándar comúnmente conocida como java.time (JSR-310). El proyecto Joda-Time recomienda ahora migrar a java.time (JSR-310).

Aunque java.time (JSR-310) se inspiró en gran medida en Joda-Time, no es compatible con versiones anteriores y los conceptos y la terminología han cambiado. Por eso, la migración de Joda-Time a java.time requiere una cuidadosa atención a cada línea de código que se cambie. Esto puede llevar mucho tiempo y casi te haría desear que hubiera una forma más fácil y automatizada de migrar.

Hay una forma mejor de migrar y la hemos creado usando Sensei - un plugin de IntelliJ para realizar automáticamente transformaciones de código según las recetas (reglas) definidas por usted. Dedique su tiempo a definir recetas reutilizables, en lugar de realizar tareas de migración repetitivas. La automatización no sólo transformará su código heredado de Joda-Time, sino que también ayudará a los equipos a seguir las directrices allí mismo, en el IDE, mientras escriben el nuevo código.

Para ayudarte a tener una ventaja, hemos creado un libro de cocina público Sensei Standardization on java.time (JSR-310) que incluye recetas para migrar de Joda-Time a java.time de una manera menos dolorosa. Se trata de un conjunto creciente de recetas que seguiremos ampliando para añadir más cobertura con más recetas.

Este es un ejemplo de una migración de muestra que puede ayudarle a ver cómo Sensei facilita la migración de código heredado.

Vídeo de cómo establecer la hora de la fecha de java.time zoned

De la migración manual repetitiva a las transformaciones de código automatizadas

Veamos un ejemplo de creación de un nuevo DateTime que demuestra unas cuantas trampas ocultas al migrar una sola línea de código de Joda-Time a java.time. A continuación, veremos una de nuestras recetas de Sensei de nuestro libro de cocina Standardization on java.time (JSR-310) y mostraremos cómo captura toda esta información, para que esta misma migración pueda ser reutilizada una y otra vez por cualquier desarrollador.

En este ejemplo, estamos construyendo un Joda-Time DateTime a partir de 7 argumentos int que representan valores de los campos DateTime.

¿Cómo podemos migrar esto a un equivalente de java.time?

El javadoc en Joda-Time para este constructor dice:

Construye una instancia a partir de los valores de los campos datetime utilizando ISOChronology en la zona horaria por defecto.

Al principio, podríamos suponer que hay una clase DateTime en java.time, pero no la hay. Si buscas en Google "migrar de joda time a java time" es muy probable que encuentres la entrada del blog de Stephen Colebourne Converting from Joda-Time to java.time.

Esto le da un buen comienzo, y nos apunta en la dirección de usar java.time.ZonedDateTime o java.time.OffsetDateTime. Aquí está nuestra primera pregunta, ¿cuál utilizo? Probablemente ZonedDateTime basado en los comentarios de Stephen.

Buscando en el javadoc de ZonedDateTime, no vemos ningún constructor. Volviendo a la entrada del blog de Stephen leemos más abajo:

Construcción. Joda-Time tiene un constructor que acepta un objeto y realiza la conversión de tipo. java.time sólo tiene métodos de fábrica, por lo que la conversión es un problema del usuario, aunque se proporciona un método parse() para las cadenas.

Así que debe haber un método estático de fábrica, buscando en los métodos estáticos encontramos uno que se parece bastante, pero no es exactamente igual.

Tiene 7 parámetros int como nuestro constructor Joda-Time DateTime original, sin embargo si no prestas atención te perderás un detalle importante. El 7º parámetro ya no representa milisegundos, en su lugar espera nanosegundos, esto se debe a que java.time ha aumentado la precisión respecto a Joda-Time y mide los instantes al nano-segundo. Un detalle importante que podría haber pasado fácilmente por alto. Además, este método espera un ZoneId, por lo que hace que te preguntes por qué no lo necesitabas antes y por qué lo necesitas ahora.

Recordando el javadoc de nuestro constructor original que mencionaba que usaría la zona horaria por defecto, ¿quizás haya una forma de obtener el ZoneId por defecto?

El javadoc de ZoneId no nos habla de ningún constructor listado, pero mirando los métodos estáticos vemos que podemos usar systemDefault()

Ahora que hemos resuelto el ZoneId, ¿qué debemos hacer con nuestra conversión de milisegundos a nanoSegundos? Tal vez podamos utilizar java.util.concurrent.TimeUnit para realizar la conversión.

Este método devuelve un long, y nuestro método espera un int, así que ahora también tenemos un problema de conversión que resolver. Tal vez podríamos intentar algo sencillo. ¿Una multiplicación?

Esto funcionará pero parece un poco fuera de lugar. Por si no lo has notado ya, hemos invertido una cantidad considerable de tiempo y esfuerzo para migrar una sola línea de código. Pero como puedes imaginar, tenemos muchas ediciones de este tipo que hacer a mano y no mejora.

Sin embargo, si miramos un poco más a la API de java.time, podemos descubrir una solución que parece un poco más fluida.

Aunque ZonedDateTime no tiene una forma obvia de establecer los milisegundos, puede hacerse mediante el método with(TemporalField field, long newValue), utilizando ChronoField.MILLI_OF_SECOND como TemporalField.

Y la documentación de java menciona que realizará la conversión a nanosegundos por nosotros:

Cuando este campo se utiliza para establecer un valor, debe comportarse de la misma manera que establecer NANO_OF_SECOND con el valor multiplicado por 1.000.000.

Así que podemos simplemente especificar 0 para nuestros nanosegundos en el método de fábrica, y luego usar el método with para crear un ZonedDateTime que tenga todos los valores originales así como los milisegundos.

Mirando nuestro resultado final, parece que sólo hemos cambiado una línea de código, ¡no muestra realmente el esfuerzo que ha supuesto investigar una sola migración!

Cree una receta para migrar más rápida y fácilmente

Sensei nos ofrece una forma de compartir con otros desarrolladores esta información que tanto nos ha costado conseguir. Al crear una receta que recoge todos estos requisitos, permitirá a los usuarios de Sensei realizar esta migración con un clic del ratón.

Una receta de Sensei consta de 3 secciones principales:

  • Metadatos
  • Buscar en
  • Arreglos disponibles

Veamos una receta de Sensei (también puede verse como una receta YAML) que nos ayudará a migrar esta llamada a su equivalente java.time.

DateTime foo = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond);

Sección de metadatos

La sección de metadatos contiene información sobre la receta y cómo debe utilizarse.

Sección de búsqueda

La sección de búsqueda de una receta de Sensei especifica a qué elementos de código debe aplicarse esta receta.

search:
instanceCreation:
args:
1:
type: int
2:
type: int
3:
type: int
4:
type: int
5:
type: int
6:
type: int
7:
type: int
argCount: 7
type: org.joda.time.DateTime

En esta sección de búsqueda vemos que estamos:

  • Buscar una instanceCreation, es decir, un uso de un Constructor. Nota: hay muchos otros objetivos de búsqueda disponibles
  • El constructor debe tener 7 argumentos, esto se especifica con la propiedad argCount
  • los argumentos 1-7 deben ser de tipo int
  • Buscamos constructores del tipo org.joda.time.DateTime

Sección de arreglos disponibles

La sección availableFixes puede especificar una o más correcciones que pueden aplicarse al elemento de código correspondiente. Cada fix puede tener múltiples acciones, y en nuestro caso tenemos un único fix, que realiza 2 acciones.

  • El nombre de la corrección se muestra al usuario en el menú "Quickfixes", y describe lo que ocurrirá si el usuario aplica esta quickfix
  • La lista de acciones muestra las acciones que realizará este quickfix
  • La acción de reescritura reescribirá el elemento de código utilizando una plantilla mustache. Puede hacer uso de variables y funciones de sustitución de cadenas.
  • La acción modifyAssignedVariable comprobará si este constructor se está utilizando para asignar el valor a una variable. Si es así, esta acción modificará la variable para que sea declarada como el tipo especificado por type

Uso de la receta para la transformación del código

Con nuestra receta escrita y habilitada, escanea nuestro código y destaca los segmentos a los que se puede aplicar.

En la captura de pantalla de abajo podemos ver que el constructor de destino ha sido marcado por Sensei. Al pasar el ratón por encima del constructor marcado nos muestra la receta shortDescription y la opción Quickfix Migrate to java.time.ZonedDateTime

Migración de la nueva fecha y hora

Después de seleccionar el quickfix Migrate to java.time.ZonedDateTime, el código se transforma según las acciones que especificamos en la receta.

Fecha Hora Zonificada Con Año Mes Día Hora

Una migración única y prácticas de codificación uniformes en todos los equipos, con Sensei

Podemos ver en nuestro ejemplo anterior que la migración de una sola línea de código puede implicar un conocimiento duramente ganado. Sensei puede convertir ese conocimiento en recetas o libros de cocina procesables que pueden ser compartidos dentro de los equipos. Puedes planificar un sprint de migración único o adoptar el enfoque de hacer transformaciones incrementales instantáneas a java.time a medida que te encuentres con código Joda-Time. Puede habilitar/deshabilitar recetas como una forma de hacer migraciones en etapas o pasos lógicos e incluso, ampliar o reducir el alcance de los archivos escaneados por Sensei - la flexibilidad que hace que las migraciones de código sean menos dolorosas.

La migración de librerías es sólo un ejemplo de las muchas maneras en que Sensei puede utilizarse para estandarizar tus proyectos. Siempre puedes estar atento a los antipatrones o a ciertas transformaciones manuales de código que encuentres con frecuencia en pull requests o mientras codificas tú mismo. Si tienes un conjunto de directrices de codificación que los desarrolladores no siguen a menudo, puedes convertir las directrices en recetas, permitiendo a los desarrolladores aplicar transformaciones de código aprobadas con confianza.

Si tienes alguna pregunta, nos encantaría que nos la hicieras llegar. Únase a nosotros en Slack en: sensei-scw.slack.com

Nos gustaría contar con su permiso para enviarle información sobre nuestros productos y/o temas relacionados con la codificación segura. Siempre trataremos sus datos personales con el máximo cuidado y nunca los venderemos a otras empresas con fines de marketing.

Enviar
Para enviar el formulario, habilite las cookies "Analytics". Siéntase libre de desactivarlas de nuevo una vez que haya terminado.