🚀The world's best VBA AI has evolved. ExcelMaster is now an autonomous Agent.Read more →
Back to Blog

VBA DateAdd y DateDiff en Excel — aritmética de fechas sin el bug de la longitud del mes

|

VBA DateAdd y DateDiff en Excel — aritmética de fechas sin el bug de la longitud del mes

TL;DR — Una fecha es un Double, así que myDate + 1 es mañana y myDate - 7 es 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; y DateDiff("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 +
  • DateDiff cuenta fronteras cruzadas, no duración transcurrida — el «1 año» a partir de un solo día
  • DateSerial construye 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 simpledate + n, date - n. Correcta y rápida; VBA pasa los meses y los años bisiestos por ti.
  • Meses / años → DateAdd → nunca + 30 ni + 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, y DateSerial(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