TL;DR — Una fecha es un
Double, así quemyDate + 1es mañana ymyDate - 7es la semana pasada — la aritmética simple es correcta para días. Se desmorona con meses y años, porque un mes no son 30 días y un año no son 365.DateAdd("m", 1, d)conoce el calendario;DateDiff("d", a, b)cuenta días enteros; yDateDiff("yyyy", a, b)cuenta fronteras de año cruzadas, no tiempo transcurrido — por eso del 31 de dic al 1 de ene hay «1 año».
Sub DateMathDemo()
Debug.Print #2026-01-31# + 1 ' 2026-02-01 - dias: el + simple funciona
Debug.Print DateAdd("m", 1, #2026-01-31#) ' 2026-02-28 - consciente del mes, ajusta
Debug.Print DateDiff("d", #2026-01-01#, #2026-03-01#) ' 59 - dias enteros entre ambas
Debug.Print DateSerial(2026, 3, 0) ' 2026-02-28 - ultimo dia de febrero
End Sub
La aritmética de fechas en VBA se apoya en un solo hecho: una fecha es un Double que cuenta días. Eso
hace que la aritmética de días sea trivial — + 1, - 7, Date + 30 funcionan sin más, porque un
día es exactamente 1. Los problemas empiezan cuando supones que el mismo truco escala a meses y
años, que no tienen longitud fija. DateAdd, DateDiff y DateSerial existen precisamente para hacer
la parte consciente del calendario que el + y el - a secas no pueden.
Lo que aprenderás
- El modelo mental — el
+/-a secas es correcto para días, erróneo para meses y años DateAdd(interval, number, date)y los códigos de intervalo que muerden ("m"vs"n","y"vs"yyyy")- Por qué
DateAdd("m", 1, 31-ene)devuelve el 28 de feb — inteligencia de fin de mes que no consigues con+ DateDiffcuenta fronteras cruzadas, no duración transcurrida — el «1 año» a partir de un solo díaDateSerialconstruye una fecha a partir de sus partes sin ambigüedad regional, y el idiom del último día del mes- Cuándo usar aritmética simple y cuándo una función es obligatoria
El modelo mental: la aritmética simple solo vale para días
Como una fecha es un Double de días, la aritmética de días no necesita función alguna:
tomorrow = Date + 1
lastWeek = Date - 7
Debug.Print #2026-02-28# + 1 ' 2026-03-01 - VBA pasa el mes por ti
Esto es genuinamente correcto — VBA se ocupa del fin de mes y de los años bisiestos cuando sumas días enteros, porque solo está sumando al número de serie. El fallo es pensar que un mes o un año son un número de días:
nextMonth = Date + 30 ' MAL - 30 dias no son un mes
nextYear = Date + 365 ' MAL - ignora el bisiesto y se desfasa
+ 30 cae en días distintos según el mes; + 365 se desvía un día en cualquier tramo que incluya el 29
de febrero. En cuanto tu unidad es mes o año, la aritmética simple es un bug y necesitas DateAdd.
DateAdd y los códigos de intervalo que muerden
DateAdd(interval, number, date) suma number unidades de interval a date. El interval es un
código de cadena, y dos colisiones causan casi toda la confusión:
| Código | Unidad | Ojo con |
|---|---|---|
"yyyy" |
Año | "y" no es año — significa día del año |
"q" |
Trimestre | |
"m" |
Mes | "m" es mes; el minuto es "n" |
"ww" |
Semana | |
"d" |
Día | "y" y "w" también se comportan como días |
"h" |
Hora | |
"n" |
Minuto | no "m" |
"s" |
Segundo |
DateAdd("m", 15, startTime) ' suma 15 MESES - probablemente no lo que querias
DateAdd("n", 15, startTime) ' suma 15 MINUTOS - la "n" que de verdad querias
El cambiazo entre "m" y "n" es el bug estrella: echas mano de "m" pensando «minuto» y has sumado en
silencio quince meses a una marca de tiempo. Di los códigos en voz alta una vez — m es mes, n es
minuto, yyyy es año — y toda la familia deja de ser propensa a errores. number puede ser negativo
para restar, y el resultado es siempre una fecha en condiciones.
Por qué DateAdd devuelve el 28 de feb cuando sumas un mes al 31 de ene
Esta es la recompensa que la aritmética simple nunca te dará. Suma un mes al 31 de enero y no hay ningún
«31 de febrero» donde aterrizar, así que DateAdd ajusta al último día válido del mes de destino:
DateAdd("m", 1, #2026-01-31#) ' 2026-02-28 (2028 daria 2028-02-29)
DateAdd("m", 1, #2026-03-31#) ' 2026-04-30 (abril tiene 30 dias)
DateAdd("yyyy", 1, #2028-02-29#) ' 2029-02-28 (el proximo no es bisiesto)
DateAdd sabe cuántos días tiene cada mes y ajusta los años bisiestos automáticamente — que es justo el
conocimiento que + 30 tira a la basura. Si estás calculando vencimientos, renovaciones o «el mismo día
del mes que viene», este ajuste es el comportamiento que quieres y la razón para no programar nunca a mano
la aritmética de meses.
DateDiff cuenta fronteras cruzadas, no tiempo transcurrido
DateDiff(interval, date1, date2) parece «cuánto tiempo hay entre dos fechas», y para "d" (días) lo es.
Para "m", "yyyy", "q" y "ww" significa algo más sutil y sorprendente: cuántas fronteras de
intervalo hay entre las dos fechas, no cuánto tiempo transcurrió de verdad.
DateDiff("yyyy", #2025-12-31#, #2026-01-01#) ' 1 - una frontera anual, pero a UN dia
DateDiff("m", #2026-01-31#, #2026-02-01#) ' 1 - una frontera de mes, a un dia
DateDiff("d", #2026-01-31#, #2026-02-01#) ' 1 - un dia real (este es literal)
Así que DateDiff("yyyy", …) es un recuento de cambios de año natural, que no es la edad de alguien
ni tampoco los años transcurridos. Para obtener un recuento real de lo transcurrido calculas la
diferencia de fronteras y luego ajustas — para la edad, resta 1 si el cumpleaños aún no ha ocurrido este
año:
age = DateDiff("yyyy", dob, Date)
If DateSerial(Year(Date), Month(dob), Day(dob)) > Date Then age = age - 1
DateDiff también devuelve un número negativo cuando date1 es posterior a date2, lo que es una
forma cómoda de comprobar el orden. La regla: usa "d" con libertad para días enteros; trata
"m"/"yyyy"/"q" como recuentos de fronteras y ajústalos antes de llamar duración al resultado.
DateSerial construye una fecha a partir de sus partes, sin ambigüedad regional
Cuando tienes el año, el mes y el día como números separados, no los pegues en una cadena esperando
que CDate los lea en el orden correcto — eso depende de la configuración regional
de la máquina y voltea día y mes en silencio al cruzar fronteras. DateSerial(year, month, day) construye
la fecha directamente, sin texto y sin ambigüedad:
d = DateSerial(2026, 2, 1) ' siempre el 1 de febrero de 2026, en toda maquina
Mejor aún, DateSerial normaliza el desbordamiento: el mes 13 pasa al año siguiente, el día 0 es
el último día del mes anterior. Eso da los dos idioms más útiles del código de fechas:
lastDayOfMonth = DateSerial(y, m + 1, 0) ' dia 0 = ultimo dia del mes m
firstOfNextQ = DateSerial(y, m + 3, 1) ' tres meses despues, dia 1
DateSerial(y, m + 1, 0) es el «último día de este mes» canónico — sin tabla de longitudes de mes, sin
caso especial de año bisiesto. Combínalo con Year, Month y Day de la
guía de extracción de partes y puedes construir cualquier fecha relativa a otra.
El veredicto honesto: los días a mano, todo lo demás por función
La línea divisoria es limpia, y quedarte del lado correcto elimina casi todos los bugs de aritmética de fechas:
- Días → aritmética simple →
date + n,date - n. Correcta y rápida; VBA pasa los meses y los años bisiestos por ti. - Meses / años →
DateAdd→ nunca+ 30ni+ 365.DateAdd("m", …)ajusta a la longitud real de los meses; recuerda que"m"es mes y"n"es minuto. - Diferencias →
DateDiff, pero léelo como fronteras →"d"son días literales;"m"/"yyyy"cuentan cruces de frontera, así que ajusta antes de llamarlo duración. - Construir desde partes →
DateSerial→ sin ambigüedad regional, yDateSerial(y, m + 1, 0)es el idiom del último día del mes.
La frase que hay que guardar: el operador + no conoce el calendario, y DateDiff no mide el tiempo. Usa
cada uno para lo único en lo que acierta.
Cómo ayuda ExcelMaster
Los bugs caros de aritmética de fechas son los plausibles: una fecha de renovación calculada como + 30
que deriva según la longitud del mes, una «edad» de DateDiff("yyyy", …) que se pasa un año cerca de los
cumpleaños, un vencimiento construido a partir de una cadena que voltea día y mes con la configuración
regional de un colega. Cada uno parece correcto en una prueba rápida y falla en los casos límite.
ExcelMaster escribe la versión
correcta según el calendario. Describe el cálculo — «el primer día laborable del mes que viene», «factura
con vencimiento a 45 días», «meses entre dos fechas» — y elige aritmética simple para los días, DateAdd
con el código de intervalo correcto para meses y años, y DateSerial para construir desde las partes sin
ambigüedad regional, convirtiendo los recuentos de fronteras de DateDiff en duraciones reales. Tú
describes la fecha que necesitas; él escribe la aritmética que sobrevive a los fines de mes y a los años
bisiestos.
Preguntas frecuentes
¿Cómo sumo días a una fecha en VBA?
Basta con aritmética: una fecha es un número de días, así que newDate = myDate + 7 suma una semana y
myDate - 1 es ayer. VBA pasa los fines de mes y los años bisiestos por ti cuando sumas días enteros.
Echa mano de DateAdd solo cuando la unidad es un mes o un año, porque esos no tienen un número fijo de
días.
¿Cuál es la diferencia entre DateAdd y usar simplemente + en VBA?
El + a secas suma días y es correcto para la aritmética de días. DateAdd(interval, number, date)
entiende el calendario, así que DateAdd("m", 1, …) suma un mes de verdad — el 31 de ene pasa a
28 de feb, no a un imposible día 31 — y DateAdd("yyyy", 1, …) gestiona los años bisiestos. Usa +
para días; usa DateAdd para meses y años, donde + 30 o + 365 derivarían.
¿Por qué DateDiff da un número de años equivocado en VBA?
Porque DateDiff("yyyy", …) cuenta fronteras de año cruzadas, no años transcurridos. Del 31 de
diciembre al 1 de enero devuelve 1, aunque solo pasó un día. Para una edad o años transcurridos de verdad,
calcula DateDiff("yyyy", dob, Date) y resta 1 si el cumpleaños de este año aún no ha ocurrido. Usa "d"
cuando quieras un recuento literal de días.
¿Qué significan los códigos de intervalo en DateAdd?
Son códigos de cadena para la unidad: "yyyy" año, "q" trimestre, "m" mes, "ww" semana, "d" día,
"h" hora, "n" minuto, "s" segundo. Las dos trampas son que el minuto es "n", no "m" (que es
el mes) y que "y" significa día del año, no año (el año es "yyyy"). Los mismos códigos funcionan en
DateDiff y DatePart.
¿Cómo obtengo el último día del mes en VBA?
Usa DateSerial con el día 0 del mes siguiente: DateSerial(Year(d), Month(d) + 1, 0). El día 0 se
define como el último día del mes anterior, así que esto devuelve el último día del mes de d sin tabla
de longitudes de mes y sin caso especial de año bisiesto. DateSerial también construye fechas a partir
de sus partes sin la ambigüedad regional de día/mes que tiene parsear una cadena.
Probado en
Probado en: Excel 365 (Windows 11), VBA 7.1 — última verificación el 26/08/2026.
Guías relacionadas: VBA Now, Date y Time · VBA Weekday y DatePart · VBA CStr, CDate y Val · VBA Format · Bucle For en VBA
