"Las fechas y las horas son algo que enseñamos a niños pequeños. ¿Qué dificultad puede tener?"
Muchos programadores han cometido ese error, y la experiencia posterior suele tener un efecto negativo en su salud y su felicidad.
Cualquier persona que haga programación no trivial con fechas y horas debería estar al menos preparada para entender y mitigar los posibles problemas.
Este documento intentará dar una visión general de cómo R maneja las fechas y las horas, pero para más detalles será necesario consultar la documentación completa.
Como lenguaje de ciencia de datos, las fechas y las horas tienen una importancia especial en R. Gran parte de los datos llegan en forma de series temporales: el número diario de pacientes durante un brote de enfermedad, las cifras mensuales de empleo que publican los gobiernos, las poblaciones de aves de una colonia de nidificación, el PIB anual de las principales economías y un sinfín de ejemplos más.
"¿Significa esto que R hace que el manejo de fechas y horas sea sencillo?"
Lamentablemente, ¡no!
La funcionalidad de fechas y horas del R base tiene mala reputación y hoy en día casi no se usa.
El paquete lubridate es una gran mejora, pero prepárate para una biblioteca grande y complicada, diseñada para manejar una gama grande y complicada de problemas.
En sus orígenes era independiente, pero ahora lubridate viene incluido en la colección tidyverse.
Cárgalo con library(lubridate) o con library(tidyverse).
Recursos que te ayudarán a trabajar con lubridate:
Una date no es más que un día, sin información de hora.
Internamente, las fechas se almacenan como el número (con signo) de días transcurridos desde 1970-01-01.
today()
#> [1] "2026-04-22"
Un datetime combina información de fecha, hora y zona horaria.
Internamente, se almacena como el número de segundos transcurridos desde el comienzo de 1970 en la zona horaria UTC.
now()
#> [1] "2026-04-22 15:37:11 MST" # mid-afternoon in Arizona
Quizá sorprendentemente, R no tiene ninguna representación interna para valores que solo contienen una hora.
date y datetime
Quizá las necesidades más frecuentes con fechas y horas sean:
today()) y/o la fecha y hora actual (con now()).Hay muchas formas de escribir fechas y horas, y suelen depender de la cultura. Las fechas con solo números, como «7/6/23», son ambiguas, confusas y han provocado muchos errores costosos en organizaciones multinacionales (el autor lo dice por amarga experiencia personal).
El estándar internacional (a-m-d) se define en ISO 8601 y tiene dos ventajas principales:
Sin embargo, la ciencia de datos consiste en trabajar con los datos que puedas encontrar, y a menudo no tiene sentido quejarse de cómo los formatea otra gente.
El R base tiene funciones de conversión que aceptan un string de formato (por defecto, ISO 8601).
as.Date("2026-04-22")
#> [1] "2026-04-22"
as.Date("4/22/26", format = "%m/%d/%y")
#> [1] "2026-04-22"
as.POSIXct("2026-04-22 15:30:09 MST")
#> [1] "2026-04-22 15:30:09 MST"
Los strings de formato se parecen a la función strptime(), que existe desde hace décadas y está incluida en muchos lenguajes.
Quienes estén familiarizados con esta sintaxis pueden usar, si lo desean, la función fast_strptime() de lubridate.
De forma más útil, lubridate ofrece una amplia variedad de funciones auxiliares, con nombres que representan el orden de los campos.
Una vez que las funciones saben qué valores buscar y en qué orden, el análisis es asombrosamente flexible.
mdy("4/22/26")
#> [1] "2026-04-22"
mdy("April 22nd 26")
#> [1] "2026-04-22"
mdy("Wednesday, April 22nd 2026")
#> [1] "2026-04-22"
mdy_hm("Wednesday, April 22nd 2026, 4:14 pm")
#> [1] "2026-04-22 16:14:00 UTC" # defaults to your OS timezone
Cuando tu programa tiene los componentes de una fecha o de una fecha y hora como números separados, puedes ensamblarlos con make_date() o make_datetime().
Estos son los componentes que se pueden proporcionar, mostrando su orden y sus valores por defecto:
make_datetime(
year = 1970L,
month = 1L,
day = 1L,
hour = 0L,
min = 0L,
sec = 0,
tz = "UTC"
)
#> [1] "1970-01-01 UTC"
make_datetime(2026, 4, 22, 17)
#> [1] "2026-04-22 17:00:00 UTC"
Como es natural, make_date() solo acepta los argumentos de año, mes y día.
La consola de R muestra automáticamente las fechas y los valores de fecha y hora como una representación en forma de string. Normalmente esto resulta cómodo, pero a veces induce a error.
Internamente, las fechas y los valores de fecha y hora son números (más unos cuantos atributos), no strings. Devolver una fecha (o una fecha y hora) cuando se espera un string causará problemas, así que necesitamos saber cómo hacer la conversión.
Un enfoque sencillo es usar as.character() del R base, que produce un string en formato ISO 8601.
También existe una función format_ISO8601() para fechas y horas, con opciones para redondear a la precisión que quieras.
today() |> as.character() # a date
#> [1] "2026-04-23"
now() |> as.character() # a datetime
#> [1] "2026-04-23 09:07:09.779319"
now() |> format_ISO8601() # defaults to nearest second
#> [1] "2026-04-23T09:07:38"
A pocas personas que no programan les apetece ver una salida en ISO 8601, así que saber manejar los formatos de fecha y hora propios de cada cultura es esencial.
El R base ofrece la función format(), que usa strings de formato similares a las venerables funciones strftime() incluidas en muchos otros lenguajes.
format(today(), "%A, %d %B %Y")
#> [1] "Thursday, 23 April 2026"
Ese enfoque del R base es feo, pero fiable. Estos son algunos de los códigos más habituales:
| Código | Resultado |
|---|---|
| %d | Número del día del mes (5, 17, 28, etc.) |
| %a | Día de la semana abreviado (Mon, Tue, Wed, etc.) |
| %A | Día de la semana completo (Monday, Tuesday, etc.) |
| %w | Número del día de la semana (0-6, el domingo es 0) |
| %u | Número del día de la semana (1-7, el lunes es 1) |
| %b | Mes abreviado (Jan, Feb, etc.) |
| %B | Mes completo (January, February, etc.) |
| %y | Año con 2 dígitos (p. ej. 89) |
| %Y | Año con 4 dígitos (p. ej. 1989) |
| %h | horas (reloj de 24 h) |
| %m | minutos |
| %s | segundos |
| %z | desplazamiento respecto a UTC |
| %Z | Zona horaria (carácter) |
Lubridate pretende ofrecer una alternativa más amigable para las personas con la función stamp(), y las más específicas stamp_date() y stamp_time().
Estas toman un string de plantilla que es un ejemplo de la salida deseada y crean una función personalizada que genera ese formato.
Lubridate intenta adivinar el formato, pero a menudo se topa con ambigüedades y produce un resultado incorrecto.
Prepárate para proporcionar strings de formato (como "dmy") que resuelvan la ambigüedad.
En Exercism, también puede ser útil especificar locale = "C", para saltarte la configuración regional de tu sistema y trabajar en inglés (como esperan los tests).
¡Las configuraciones regionales son complicadas!
sf <- stamp("Saturday, June 27, 2026 at 14:45", locale = "C")
#> Multiple formats matched: "%A, %B %d, %Y at %H:%M"(1), "Saturday, %Om %d, %Y at %H:%M"(1), "Saturday, %B %d, %Y at %H:%M"(1), "%A, %Om %d, %Y at %H:%M"(0)
#> Using: "%A, %B %d, %Y at %H:%M"
sf(make_datetime(2023, 3, 29, 15, 0, 0))
#> [1] "Wednesday, March 29, 2023 at 15:00"
Lubridate ofrece muchas funciones para obtener o establecer elementos de una fecha o de una fecha y hora: las más obvias, como year() y second(), pero también otras más especializadas, como quarter() y epiweek().
Todas estas funciones sirven para obtener un valor, y la mayoría también para establecerlo.
dt <- now()
dt
#> [1] "2026-04-23 15:47:11 MST"
# get
month(dt)
#> [1] 4
# set
month(dt) <- 11
dt
#> [1] "2026-11-23 15:47:11 MST"
Para wday() (día de la semana) y month(), la salida por defecto es un número entero, pero:
labels = TRUE devuelve un string abreviado correspondiente a la configuración regional actual.abbr = FALSE devuelve el nombre completo.d <- today()
d
#> [1] "2026-04-23"
wday(d)
#> [1] 5
wday(d, label = TRUE)
#> [1] Thu
#> Levels: Sun < Mon < Tue < Wed < Thu < Fri < Sat
wday(d, label = TRUE, abbr = FALSE)
#> [1] Thursday
#> Levels: Sunday < Monday < Tuesday < Wednesday < Thursday < Friday < Saturday
Las etiquetas son categorical variables, que serán el tema de otro concepto.
Los valores están restringidos a los Levels listados, que además muestran el orden de clasificación.
La semana empieza en domingo por defecto, pero esto puede cambiarse con el argumento week_start (usa 1 para el lunes).
dt <- make_datetime(2026, 4, 23)
# default behavior
wday(dt)
#> [1] 5
wday(dt, label = T)
#> [1] Thu
# set week_start to Monday
wday(dt, week_start = 1)
#> [1] 4
Unas cuantas funciones de utilidad devuelven valores TRUE/FALSE, como leap_year() y dst() (¿está activo el horario de verano?).
Los meses tienen distinta duración, así que days_in_month() da el valor entero.
Tanto los valores date como los datetime representan un instante a lo largo de la línea temporal.
¿Cómo manejamos conceptos como «a esta hora ayer» o «dentro de una semana»?
Quienes conozcan otros lenguajes orientados a la ciencia quizá hayan oído hablar de datetime.timedelta en Python o del tipo Period en Julia.
En cambio, R tiene nada menos que tres formas de representar lapsos de tiempo, con distintas ventajas y limitaciones. Como siempre, elige la que mejor se adapte a tu contexto.
Los distintos lapsos pueden convertirse entre sí fácilmente con as.period(), as_duration() y as.interval().
Los tipos de lapso pueden consultarse con is.period(), etc.
Un valor period, como hours(3), puede usarse para incrementar una hora de reloj en 3 horas.
Las irregularidades como el horario de verano se ignoran en gran medida.
Todos los periodos son el nombre de una unidad en plural: years, weeks, etc.
d <- today()
d
#> [1] "2026-04-25"
d + weeks(2)
#> [1] "2026-05-09"
# dates are coerced to datetimes at midnight if necessary
d - hours(20)
#> [1] "2026-04-24 04:00:00 UTC"
hours(3)
#> [1] "3H 0M 0S"
Una duration, como dhours(3), incrementa el tiempo físico en 3 horas, respetando irregularidades como el horario de verano, que hacen que la hora de reloj se desvíe del tiempo físico.
Todas las duraciones añaden un prefijo d al periodo correspondiente: dyears, dweeks, etc.
dhours(3)
#> [1] "10800s (~3 hours)"
Un interval es una combinación de una hora de inicio y una de fin, creado con la función interval() o con el operador infijo %--%.
# the next 2 lines were run about 33s apart, so now() is different
dt1 <- now()
dt2 <- now() + hours(1)
# interval(0 function)
ivl <- interval(dt1, dt2)
ivl
#> [1] 2026-04-24 16:24:54 MST--2026-04-24 17:25:27 MST
# infix operator
iv2 <- dt1 %--% dt2
iv2 == iv1
#> [1] TRUE
Lidiar con las zonas horarias puede resultar complicado. La cita siguiente procede de la documentación de Python, pero es igualmente aplicable a R:
«Las reglas para el ajuste de la hora en todo el mundo son más políticas que racionales, cambian con frecuencia y no hay ningún estándar válido para todas las aplicaciones salvo UTC.»
Algunos puntos generales sobre las zonas horarias:
datetime siempre incluye una zona horaria (tz); un date no tiene en cuenta las zonas horarias.now() es una excepción: obtiene tu hora local del reloj del sistema, incluida la tz.
Por defecto se usa la tz local, pero pueden especificarse otras, como now("UTC").# timezone defaults to UTC
local <- ymd_hm("2026-04-25 10:32")
local
#> [1] "2026-04-25 10:32:00 UTC"
# timezone can be specified
AZ <- ymd_hm("2026-04-25 10:32", tz = "America/Phoenix")
AZ
#> [1] "2026-04-25 10:32:00 MST"
# same clocktime, different tz
local - AZ
#> Time difference of -7 hours
La aritmética relacionada con zonas horarias es más fiable con duraciones que con periodos.
Trabajar con fechas y valores de fecha y hora suele ser mucho más fácil con lubridate que con el R base.
Este concepto ha intentado dar una visión general, pero inevitablemente se deja muchísimos detalles.
A riesgo de repetirme: la documentación es tu amiga, así que úsala, por favor.
En este ejercicio trabajarás en un planificador de citas para un salón de belleza de Nueva York que abrió el 15 de septiembre de 2012.
Tienes cinco tareas, todas relacionadas con fechas de citas. Las horas se expresan en formato de 24 horas (sin AM/PM). Para que los problemas de zona horaria sean lo más sencillos posible, da por hecho que todo usa UTC.
Implementa la función schedule_appointment() para analizar una representación textual de una fecha de cita y convertirla al formato DateTime correspondiente.
Ten en cuenta que la entrada está en el orden mes/día/año, al estilo estadounidense.
julia> schedule_appointment("07/25/2023 13:45:00")
2023-07-25T13:45:00
Implementa la función has_passed(), que recibe una fecha de cita y comprueba si la cita fue en algún momento del pasado:
julia> has_passed(DateTime(1999, 12, 31, 9, 0, 0))
true
Implementa la función is_afternoon_appointment(), que recibe una fecha de cita y comprueba si la cita es por la tarde (>= 12:00 y < 18:00):
julia> is_afternoon_appointment(DateTime(2023, 3, 29, 15, 0, 0))
true
Implementa la función day_of_week(), que devuelve el día de la semana numérico.
El salón cierra los domingos, así que el lunes es el día 1 y el sábado el día 6.
# a Friday
day_of_week(make_datetime(2026, 06, 05, 12, 30, 00))
#> [1] 5
Cuando un cliente necesita reprogramar una cita, esta se reprograma automáticamente para el viernes siguiente a la misma hora del día (ya que es el día más tranquilo de la semana).
Implementa la función reschedule(), que recibe la cita original (como un string de texto en orden mes/día/año) y devuelve una cita reprogramada.
# a Saturday
reschedule("06/13/2026 12:30:00")
#> [1] "2026-06-19 12:30:00 UTC"
Si la cita original era de lunes a jueves, reprográmala para el viernes de esta semana. Para las citas de viernes o sábado, reprográmalas para el viernes de la semana siguiente.
Regístrate en Exercism para aprender y dominar R con 21 conceptos111 ejercicios y mentoría humana real, todo gratis.