Date functions: calculate periods and build safe limits
Dates represent instants—specific points in time—or periods—intervals between two boundaries—not text with a visual format. We use temporal functions and suitable types to add days, measure differences, obtain the end of a month, or construct a date.
One of the most important decisions is to correctly express ranges when the column also contains time.
Date operations
DATEADD: shift a date
DATEADD adds an amount in the specified unit. A negative amount moves the date backward.
DATEADD(Unidad, Cantidad, Fecha)
The unit can be DAY, MONTH, or YEAR, among others. It is not written as a quoted string.
DATEDIFF: count crossed boundaries
DATEDIFF counts how many boundaries of the specified unit occur between a start and an end. It does not measure an exact fractional duration.
DATEDIFF(Unidad, FechaInicial, FechaFinal)
DATEFROMPARTS: construct a date
DATEFROMPARTS accepts year, month, and day as numbers and returns a DATE value. If the combination is not a valid date, the function raises an error.
DATEFROMPARTS(Anio, Mes, Dia)
EOMONTH: get the last day of a month
EOMONTH returns the date for the last day of the month containing the supplied value.
EOMONTH(Fecha)
SYSDATETIME: read the server’s local clock
SYSDATETIME returns the server’s date and time with DATETIME2 precision. It takes no arguments and does not automatically represent the user’s time zone.
SYSDATETIME()
Example: add a period
SELECT
p.IdPedido,
p.FechaPedido,
DATEADD(DAY, 5, p.FechaPedido) AS FechaEstimada
FROM dbo.Pedido AS p;
The example moves each date five days forward to produce an estimated date.
Measure a difference
SYSDATETIME() takes the current time of the server’s local clock. It takes no arguments; The parentheses are part of the function call.
SELECT
c.IdCliente,
c.FechaRegistro,
DATEDIFF(DAY, c.FechaRegistro, SYSDATETIME()) AS DiasDesdeRegistro
FROM dbo.Cliente AS c;
DATEDIFF counts how many day boundaries were crossed. It does not calculate an exact decimal duration. Two instants separated by a few seconds can produce 1 day if they fall on opposite sides of midnight.
Build the start and end of a month
DECLARE @Anio INT = 2026;
DECLARE @Mes INT = 3;
SELECT
DATEFROMPARTS(@Anio, @Mes, 1) AS InicioMes,
EOMONTH(DATEFROMPARTS(@Anio, @Mes, 1)) AS FinMes;
Variables created using DECLARE are explained separately in DECLARE, SET, and SELECT.
DATEFROMPARTS avoids depending on the language or regional format of a string.
Filter a range containing time
To retrieve every March order, we use an inclusive lower boundary, which admits the boundary value, and an exclusive upper boundary, which leaves it out:
DECLARE @Desde DATE = '2026-03-01';
DECLARE @Hasta DATE = '2026-04-01';
SELECT
p.IdPedido,
p.FechaPedido,
p.Estado
FROM dbo.Pedido AS p
WHERE p.FechaPedido >= @Desde
AND p.FechaPedido < @Hasta
ORDER BY p.FechaPedido;
This form includes any time precision within March. Avoid writing an ending like 23:59:59, which can skip values with greater precision.
Calculate next month
When we only receive an initial date, the exclusive limit can be derived:
DECLARE @InicioMes DATE = '2026-03-01';
DECLARE @InicioSiguienteMes DATE = DATEADD(MONTH, 1, @InicioMes);
What should be checked
We must distinguish DATE, DATETIME, DATETIME2, and values with a zone or offset. An offset is the numerical difference from UTC attached to an instant. SYSDATETIME() uses the server’s local clock; it does not automatically represent the user’s zone.
We also avoid converting the column to text inside the filter. Comparing a temporal range preserves the meaning of the data and usually provides a better chance of using an index, a structure that organizes values to make rows easier to access.