Una de mis funciones favoritas de PHP es el strtotime
función. Esta función le permite pasar una cadena de fecha y devolver una marca de tiempo de Unix
$time = strtotime('2021-04-01');
echo date('c',$time),"\n";
// outputs
// 2021-04-01T00:00:00-07:00
Lo bueno de esto es que funciona con una variedad de formatos de fecha.
$time = strtotime('04/01/2021');
echo date('c',$time),"\n";
// outputs
// 2021-04-01T00:00:00-07:00
Y no se preocupe:si son todos objetos todo el tiempo, el mismo comportamiento de análisis de cadenas funciona con DateTime
de PHP. clase.
$date = new DateTime('April 1, 2020');
echo $date->format('c'),"\n";
// outputs
// 2020-04-01T00:00:00-07:00
Con strtotime
si está trabajando con datos incompletos (en otras palabras, datos reales), tiene un poco más de confianza en que su código seguirá funcionando cuando/si su código encuentra un formato de fecha inesperado.
Fecha.parse de Javascript
Javascript tiene una funcionalidad similar integrada en su Date
objeto. Desafortunadamente, hay algunos casos extraños en torno a las zonas horarias que lo hacen poco confiable. Todos los siguientes ejemplos usan un REPL de Node.js 14.2, pero deberían aplicarse en general a las versiones modernas de javascript.
En javascript, puede usar el Date.parse
para analizar automáticamente una cadena de fecha y recuperar una marca de tiempo de Unix, o puede pasar una cadena directamente al Date
función constructora del objeto.
$ node
Welcome to Node.js v14.2.0.
Type ".help" for more information.
> Date.parse('April 1, 2021')
1617260400000
> new Date('April 1, 2021')
2021-04-01T07:00:00.000Z
Inmediatamente vemos algunas pequeñas diferencias con respecto a strtotime
. Primero, javascript informa sus marcas de tiempo de época de Unix en milisegundos, no en segundos. En segundo lugar, el formato de fecha ISO de javascript (el 'c'
formato en PHP stringToTime
) siempre informa utilizando la hora UTC (indicada por el Z
final) ), donde PHP informa el desplazamiento de la zona horaria desde UTC. Así que estas dos cadenas de fecha ISO
2021-04-01T00:00:00-07:00
2021-04-01T07:00:00.000Z
representan la misma hora.
Nota :Todos los ejemplos de este artículo se ejecutaron en una computadora configurada para la hora de la costa oeste de EE. UU. durante el horario de verano; es posible que vea una diferencia de siete horas según cuándo y dónde ejecute los ejemplos de código.
Hasta ahora, estas son diferencias importantes, pero pequeñas. La mayor diferencia surge cuando comienza a usar cadenas de fecha que parecen ser parte de una cadena de fecha ISO 8601
> new Date('2021-04-01')
2021-04-01T00:00:00.000Z
Verás que, como antes, javascript usa un Z
para indicar que la fecha está en hora UTC. Sin embargo, también notará que la hora no 07:00:00
-- es 00:00:00
. En nuestros ejemplos anteriores, javascript asume una hora de medianoche usando el actual zona horaria configurada. Sin embargo, cuando usamos 2021-04-01
como una cadena de fecha, javascript asumió una hora de medianoche con una zona horaria UTC. Porque 2021-04-01
parece una fecha ISO 8601 incompleta, javascript asumió que era una fecha ISO 8601 con una zona horaria faltante y la zona horaria predeterminada es UTC.
Si no lo sabe, este comportamiento puede causar errores en su programa. Me encontré con esto cuando estaba procesando algunos archivos CSV de los bancos. Algunas transacciones aparecieron en el día equivocado porque un archivo CSV usaba YYYY-MM-DD
formato y otro usó el MM/DD/YYYY
formato.
Este no es el único problema con el análisis de cadenas en el Date
clase. La documentación de MDN sobre el formato de cadena de fecha y hora de javascript cubre algunos otros casos extremos que podrían interesarle.
Bibliotecas de fechas
El Date
object es uno de los objetos originales de javascript y no es probable que cambie su comportamiento. Si algún proveedor de javascript "arregló" esto para que fuera más consistente, es casi seguro que rompería una gran cantidad de código en el mundo. Debido a esto, la mayoría de los programadores de JavaScript confían en una biblioteca de terceros para manejar las fechas.
Veamos cuatro bibliotecas populares de manejo de fechas (date-fns
, dayjs
, luxon
y moment
) y vea cómo manejan YYYY-MM-DD
caso. Los siguientes ejemplos suponen que tiene estas bibliotecas de fechas instaladas a través de npm.
$ npm install date-fns dayjs luxon moment
Momento
La biblioteca de momentos es una de las bibliotecas de fechas más populares para javascript, incluso si sus desarrolladores se han alejado de ella y la consideran "terminada". Veamos cómo maneja cadenas de fecha ISO abreviadas.
> moment= require('moment')
//...
> moment('2021-04-01')
Moment<2021-04-01T00:00:00-07:00>
¡Éxito! A diferencia del Date
nativo objeto, el momento no asume una zona horaria UTC. En su lugar, asume la zona horaria del sistema configurada actualmente.
Sin embargo, sucederá algo interesante si tratamos de analizar una cadena de fecha que no tiene formato ISO.
> moment('04/01/2021')
Deprecation warning: value provided is not in a recognized RFC2822 or
ISO format. moment construction falls back to js Date(), which is not
reliable across all browsers and versions. Non RFC2822/ISO date formats
are discouraged.
Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.
/* ... */
Moment<2021-04-01T00:00:00-07:00>
El moment
la función aún devuelve una fecha, pero recibimos una advertencia de que nuestra fecha está en un formato que ese momento no reconoce, y ese momento está recurriendo al uso de javascript integrado en Date
. Entonces, aunque obtuvimos la respuesta que queríamos para nuestra fecha ISO 8601 (Moment<2021-04-01T00:00:00-07:00>
), es posible que no tengamos tanta suerte si usáramos una versión diferente de javascript o un formato de cadena que no estuviera basado en ISO 8601.
Luxón
La biblioteca de fechas de luxon (creada por uno de los mantenedores de moment) tiene un enfoque diferente.
Luxon puede manejar una variedad de formatos de fecha, pero no intenta detectar automáticamente qué formato es cuál.
const {DateTime} = require('luxon')
DateTime.fromISO(...)
DateTime.fromRFC2822(...)
DateTime.fromSQL(...)
DateTime.fromMillis(...)
DateTime.fromSeconds(...)
DateTime.fromJsDate(...)
La filosofía de Luxon es que depende de usted, el programador usuario final, saber con qué tipo de fechas está tratando. Si llama a uno de estos métodos con un formato de fecha no válido, Luxon devolverá un DateTime
objeto, pero ese objeto se considerará inválido
> DateTime.fromISO('04/01/2021')
DateTime {
/* ... */
invalid: Invalid {
reason: 'unparsable',
explanation: `the input "04/01/2021" can't be parsed as ISO 8601`
},
/* ... */
}
Día.js
El siguiente es Day.js, una biblioteca que se enorgullece de su pequeño tamaño y una API similar a Moment.js.
Day.js parece capaz de analizar una variedad de formatos de fecha, y no queda atrapado en el problema ISO 8601 UTC.
> const dayjs = require('dayjs')
undefined
> dayjs('2021-04-01')
d {
/* ... */
'$d': 2021-04-01T07:00:00.000Z,
/* ... */
}
> dayjs('4/01/2021')
d {
/* ... */
'$d': 2021-04-01T07:00:00.000Z,
/* ... */
}
Sin embargo, su página de documentos contiene esta vaga advertencia.
Esto sugiere que, detrás de escena, Day.js está realizando una validación y análisis de datos adicionales, pero en última instancia solo usa un Date
objeto para su análisis. Dado que Day.js es de código abierto, podemos echar un vistazo entre bastidores y confirmar que esto es cierto.
Esto significa que si está utilizando Day.js y desea un análisis coherente de las fechas que no son ISO, deberá usar su CustomParseFormat
enchufar. El complemento le permite definir un formato de cadena que analizará una cadena de fecha específica.
> const dayjs = require('dayjs')
/* ... */
> const customParseFormat = require('dayjs/plugin/customParseFormat')
/* ... */
> dayjs.extend(customParseFormat)
/* ... */
> dayjs('04/01/2021', 'MM/DD/YYYY')
d {
/* ... */
'$d': 2021-04-01T07:00:00.000Z,
/* ... */
}
Si su fecha tiene un formato conocido y usa uno de los tokens de análisis de Day.js, estará en buena forma.
fecha-fns
La última biblioteca de fechas que veremos es date-fns
, que se describe a sí mismo como
El date-fns
La biblioteca se enorgullece de su tamaño, con más de 200 funciones en su GitHub README. Cuando se trata del análisis de fechas, date-fns
tiene un parseISO
función que es explícitamente para analizar cadenas de fecha ISO completas y parciales.
> const datefns = require('date-fns')
//...
> datefns.parseISO('2021-04-01')
2021-04-01T07:00:00.000Z
Al igual que las otras soluciones basadas en bibliotecas, esta función utilizará la zona horaria actual si no se proporciona una.
Si tu cita es no una cadena similar a ISO, datefns
proporciona una solución basada en cadenas de formato a través de parse
método. Similar a Day.js, el parse
El método te permite decirle a datefns
cómo debe analizar una cadena de fecha.
> foo = datefns.parse('04/01/2021','MM/dd/yyyy', (new Date))
2021-04-01T07:00:00.000Z
Ese tercer parámetro requerido es un Date
objeto -- según los documentos, parse
usará este objeto para
Lo que esto significa en la práctica lo dejaremos como ejercicio para el lector; para el caso general, esto significa pasar un nuevo Date
instancia.
Otra cosa a tener en cuenta aquí:esos tokens de formato no son los mismos tokens que se usan en otras bibliotecas.
Cambio de responsabilidad
Como puede ver, hay una variedad de bibliotecas y enfoques disponibles para que un desarrollador de javascript evite el comportamiento predeterminado no ideal del Date
de javascript. objeto. Sin embargo, también puede haber notado que ninguna de sus bibliotecas intenta resolver el problema del análisis de cadenas de fecha genéricas. En su lugar, ofrecen al programador-usuario final una variedad de opciones para manejar cadenas de fechas, pero es responsabilidad del programador del cliente identificar qué formato están usando sus fechas.
Dicho de otra manera, si tiene un archivo CSV bancario que incluye fechas en el formato
04/01/2021
escribirá una cadena de formato para analizar este formato de fecha específico, o analizará usted mismo su cadena de fecha en sus partes de mes/día/año. Si tiene una fuente de datos donde el formato de fecha varía, estará escribiendo código para identificar qué formato es ese.
Esto encaja con la tendencia general del código fuente abierto durante los últimos 5 a 10 años. La mayoría de las veces, los creadores y mantenedores de bibliotecas de software intentan limitar el alcance de lo que hace el código que publican en el mundo para limitar el alcance de lo que necesitan respaldar en el futuro.
¿Portar strtotime?
Después de hacer toda esta investigación, tenía una última pregunta:¿por qué no simplemente transferir strtotime
? a otros idiomas? Fui a buscar y encontré dos cosas que vale la pena mencionar.
Primero, la implementación de strtotime
es un estudio de libro de texto sobre por qué el código C de otras personas no es donde usted quiere pasar el tiempo. Puede ver las agallas de la lógica de implementación aquí. Este no es un código C estándar, es un código para un sistema llamado re2c. Este sistema le permite escribir expresiones regulares en un DSL personalizado (lenguaje específico de dominio) y luego transformar/compilar esas expresiones regulares en programas C (también C++ y Go) que ejecutarán esas expresiones regulares. Algo en el archivo make de PHP usa este parse_date.re
archivo para generar parse_date.c
. Si no te das cuenta parse_date.c
es un archivo generado, esto puede ser extremadamente difícil. Si no está familiarizado con re2c
Puede ser regular y rudo. Dejamos la exploración adicional como un ejercicio para el lector, un ejercicio que no hemos realizado nosotros mismos.
Por lo tanto, migrar esta función no es una tarea sencilla, pero hay un paquete de código abierto impulsado por la comunidad llamado locutus que lo está intentando. En sus propias palabras
Este paquete incluye una implementación de strtotime
de PHP función. Si bien no es un puerto directo del re2c
Expresiones regulares de PHP, parece manejar los formatos de fecha que hemos usado en este artículo. Un programa como este
const strtotime = require('locutus/php/datetime/strtotime')
console.log(new Date(strtotime('April 1, 2021') * 1000))
console.log(new Date(strtotime('4/1/2021') * 1000))
console.log(new Date(strtotime('2021-04-01') * 1000))
da como resultado una salida como esta
2021-04-01T07:00:00.000Z
2021-04-01T07:00:00.000Z
2021-04-01T07:00:00.000Z
Fechas idénticas, creadas con una fecha de medianoche en la zona horaria local, representada como una fecha UTC.