«Las fechas y las horas son algo que les enseñamos a los niños pequeños. ¿Qué tan difícil puede ser?»
Muchos programadores han cometido ese error, y la experiencia que viene después suele tener un mal efecto en su salud y su felicidad.
Cualquiera que haga programación no trivial con fechas y horas debería estar al menos preparado para entender y mitigar 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, R le da una importancia especial a las fechas y las horas. Gran parte de los datos llega en forma de series temporales: el número diario de pacientes durante un brote de enfermedad, las cifras mensuales de empleo que reportan los gobiernos, las poblaciones de aves en una colonia de anidación, el PIB anual de las grandes economías, y un sinfín de ejemplos más.
¿Esto significa que R hace que el manejo de fechas y horas sea sencillo?
¡Lamentablemente, no!
La funcionalidad de fechas y horas en Base R tiene mala reputación y en su mayor parte hoy se usa poco.
El paquete lubridate es una gran mejora, pero prepárate para una biblioteca grande y complicada, diseñada para manejar una gama amplia y complicada de problemas.
Originalmente independiente, lubridate ahora viene incluido en la colección tidyverse.
Cárgalo con library(lubridate) o library(tidyverse).
Algunos recursos que te ayudarán a trabajar con lubridate:
Una date es solo un día, sin información de hora.
Internamente, las fechas se guardan como el número (con signo) de días desde 1970-01-01.
today()
#> [1] "2026-04-22"
Una datetime combina información de fecha, hora y zona horaria.
Internamente, se guarda como el número de segundos desde el inicio de 1970 en la zona horaria UTC.
now()
#> [1] "2026-04-22 15:37:11 MST" # mid-afternoon in Arizona
Quizás te sorprenda que R no tenga una representación interna para valores que solo son hora.
date y datetime
Quizás las necesidades de fecha y hora más frecuentes sean:
today()) o la fecha y hora actual (con now()), o ambas.Hay muchas maneras de escribir fechas y horas, y tienden a ser específicas de cada cultura. Las fechas solo con 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) está definido en ISO 8601, y tiene dos ventajas principales:
Sin embargo, la ciencia de datos se trata de trabajar con los datos que puedas encontrar, y a menudo no tiene sentido quejarse de cómo los formatea otra gente.
Base R tiene funciones de conversión que toman un string de formato (con ISO 8601 como valor predeterminado).
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.
Si ya conoces esta sintaxis, puedes usar opcionalmente 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 sorprendentemente 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 fecha y hora como números separados, puedes ensamblarlos con make_date() o make_datetime().
Estos son los componentes que puedes proporcionar, mostrando su orden y sus valores predeterminados:
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"
Naturalmente, make_date() solo toma 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 string. Por lo general es cómodo, pero a veces engañoso.
Internamente, tanto las fechas como los valores de fecha y hora son números (más algunos atributos), no strings. Devolver una fecha (o 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() de Base R, que devuelve un string en formato ISO 8601.
También existe una función format_ISO8601() para fechas y horas, que tiene 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 ajenas a la programación les interesa ver una salida en ISO 8601, así que manejar formatos de fecha y hora específicos de cada cultura es esencial.
Base R 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 de Base R es feo, pero confiable. Estos son algunos de los códigos más comunes:
| 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 de 2 dígitos (por ejemplo, 89) |
| %Y | Año de 4 dígitos (por ejemplo, 1989) |
| %h | horas (reloj de 24 horas) |
| %m | minutos |
| %s | segundos |
| %z | desplazamiento respecto a UTC |
| %Z | Zona horaria (carácter) |
Lubridate busca ofrecer una alternativa más amigable para las personas con la función stamp(), y con 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 para generar ese formato.
Lubridate intenta adivinar el formato, pero a menudo se topa con ambigüedades y produce el resultado equivocado.
Prepárate para proporcionar strings de formato (como "dmy") para desambiguar.
En Exercism, también puede ser útil especificar locale = "C" para omitir la configuración regional de tu sistema y trabajar en inglés (como esperan las pruebas).
¡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 fecha y hora: las obvias, como year() y second(), pero también otras más especializadas, como quarter() y epiweek().
Todas estas funciones se pueden usar para obtener un valor, y la mayoría también se puede usar para establecer el valor.
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 predeterminada es un número entero, pero:
labels = TRUE devuelve un string de caracteres abreviado que corresponde 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 ordenamiento.
De forma predeterminada, la semana empieza el domingo, pero esto se puede cambiar 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
Algunas funciones de utilidad devuelven valores TRUE/FALSE, como leap_year() y dst() (¿está activo el horario de verano?).
Los meses varían en duración, así que days_in_month() da el valor entero.
Tanto los valores date como los datetime representan un instante sobre la línea de tiempo.
¿Cómo manejamos conceptos como «a esta hora ayer» o «dentro de una semana»?
Si ya conoces otros lenguajes orientados a la ciencia, quizás hayas 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 fortalezas y limitaciones. Como siempre, elige la que mejor se adapte a tu contexto.
Los distintos lapsos se pueden convertir fácilmente entre sí con as.period(), as_duration() y as.interval().
Los tipos de lapso se pueden consultar con is.period(), etc.
Un period, como hours(3), se puede usar para incrementar una hora de reloj en 3 horas.
Las irregularidades, como el horario de verano (DST), se ignoran en gran medida.
Todos los períodos 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 DST que hacen que el tiempo de reloj se desvíe del tiempo físico.
Todas las duraciones agregan el prefijo d al período correspondiente: dyears, dweeks, etc.
dhours(3)
#> [1] "10800s (~3 hours)"
Un interval es una combinación de un tiempo de inicio y uno de fin, creada 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 ser todo un desafío. La siguiente cita proviene de la documentación de Python, pero es igualmente relevante para R:
«Las reglas para ajustar el tiempo en todo el mundo son más políticas que racionales, cambian con frecuencia, y no existe un estándar adecuado para todas las aplicaciones aparte de UTC.»
Algunos puntos generales sobre las zonas horarias:
datetime siempre incluye una zona horaria (tz); una date no tiene en cuenta la zona horaria.datetime por lo general usan UTC de forma predeterminada.now() es la excepción: obtiene tu hora local del reloj del sistema, incluida la tz.
De forma predeterminada se usa la tz local, pero se pueden especificar 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 confiable con duraciones que con períodos.
Trabajar con fechas y valores de fecha y hora suele ser mucho más fácil con lubridate que con Base R.
Este concepto ha intentado dar una visión general, pero inevitablemente deja fuera muchísimos detalles.
A riesgo de repetirme: la documentación es tu amiga, así que úsala, por favor.
En este ejercicio vas a trabajar en un sistema para agendar citas de un salón de belleza en Nueva York que abrió el 15 de septiembre de 2012.
Tienes cinco tareas, y todas tienen que ver con fechas de citas. Las horas están en formato de 24 horas (sin AM/PM). Para simplificar al máximo los problemas de zona horaria, asume 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 está cerrado los domingos, así que el lunes es el día 1 y el sábado es 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 siguiente viernes 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, reprograma para el viernes de esta semana. Para las citas de viernes o sábado, reprograma 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.