SQL Server has an EOMONTH() function that returns the last day of the month for a given date. It can be quite handy when you’re working with date calculations in your queries, as it saves you from having to perform calculations just to get the end of the month.
What EOMONTH() Does
The EOMONTH() simply takes a date and returns the last day of that month. If you pass it January 15th, 2026, it’ll give you back January 31st, 2026. Pass it February 3rd, 2024, and you get February 29th, 2024 (since 2024 is a leap year). The function automatically handles all the quirks of the calendar. For example, it automatically calculates leap years, months with 30 days versus 31 days, February’s special behavior, etc.
Before this function was introduced in SQL Server 2012, we had to write complex expressions involving DATEADD() and DATEDIFF() to achieve the same result. Now you can do it with a single, readable function call.
Basic Syntax
The syntax for EOMONTH() goes like this:
EOMONTH(start_date [, month_to_add])
The first argument, start_date, is required and represents the date you want to work with. The second argument, month_to_add, is optional and lets you offset the calculation by a certain number of months. This offset can be positive (to go forward in time) or negative (to go backward).
Simple Example
Let’s start with the most basic usage. If you want to find the last day of the current month:
SELECT EOMONTH(GETDATE()) AS LastDayOfCurrentMonth;
This returns the last day of whatever month you’re currently in. If today is March 15th, 2026, this query returns March 31st, 2026.
You can also use it with a specific date:
SELECT EOMONTH('2026-06-15') AS LastDayOfJune;
The function automatically figures out that June has 30 days and returns the appropriate date.
Using the Month Offset Parameter
The (optional) second argument allows us to calculate the end of months in the past or future relative to your starting date.
To get the last day of next month:
SELECT EOMONTH(GETDATE(), 1) AS LastDayOfNextMonth;
If you run this in March 2026, it returns April 30th, 2026. The function first determines what “next month” means relative to the current date, then finds the last day of that month.
Going backward works just as well:
SELECT EOMONTH(GETDATE(), -1) AS LastDayOfPreviousMonth;
You can use larger offsets too. Here’s how to get the last day of the month six months from now:
SELECT EOMONTH(GETDATE(), 6) AS LastDaySixMonthsOut;
Calculating Monthly Totals
One common use case for EOMONTH() is in financial reporting, where you often need to group or filter data by month-end dates.
Let’s say you have a sales table and want to calculate monthly totals with the month-end date:
SELECT
EOMONTH(OrderDate) AS MonthEnd,
SUM(OrderAmount) AS TotalSales
FROM Orders
WHERE OrderDate >= '2024-01-01'
GROUP BY EOMONTH(OrderDate)
ORDER BY MonthEnd;
This query groups all orders by their month and shows each month’s total alongside its end date, making it great for monthly reports.
Another scenario involves finding the first day of a month. While EOMONTH() gives you the last day, you can use it to calculate the first day too:
SELECT DATEADD(DAY, 1, EOMONTH(GETDATE(), -1)) AS FirstDayOfCurrentMonth;
This works by getting the last day of the previous month, then adding one day to it.
Working with Subscription and Billing Cycles
EOMONTH() can be useful when dealing with subscriptions or billing periods. Imagine you have a subscription system where users are billed on the last day of each month. You can easily calculate upcoming billing dates:
SELECT
UserID,
SubscriptionStartDate,
EOMONTH(SubscriptionStartDate) AS FirstBillingDate,
EOMONTH(SubscriptionStartDate, 1) AS SecondBillingDate,
EOMONTH(SubscriptionStartDate, 2) AS ThirdBillingDate
FROM Subscriptions;
This gives you a clear view of when each user will be billed over the next few months.
Handling Date Ranges
When you need to query data for a complete month, EOMONTH() makes it easy to define your date range precisely. Here’s how you might pull all orders from the previous complete month:
SELECT *
FROM Orders
WHERE OrderDate >= DATEADD(DAY, 1, EOMONTH(GETDATE(), -2))
AND OrderDate <= EOMONTH(GETDATE(), -1);
This query finds the last day of the previous month (using -1), then goes back to the day after the last day of the month before that (using -2), giving you exactly one complete month of data.
Data Type Considerations
EOMONTH() accepts various date-related data types as input, including date, datetime, datetime2, datetimeoffset, smalldatetime, and even varchar representations of dates. The function always returns a date type though. This is important to remember because the returned value doesn’t include a time component.
If you pass in a datetime value like ‘2024-03-15 14:30:00‘, EOMONTH() returns ‘2024-03-31‘ with no time portion. If you need to maintain the time component or set it to a specific value, you’ll need to convert or cast the result:
SELECT CAST(EOMONTH(GETDATE()) AS DATETIME) + CAST('23:59:59' AS DATETIME) AS EndOfMonthWithTime;
That hardcodes the time component. You could also generate the time component like this:
SELECT DATEADD(MILLISECOND, -3, DATEADD(DAY, 1, CAST(EOMONTH(GETDATE()) AS DATETIME))) AS EndOfMonthEndOfDay;
Or using datetime2:
SELECT DATEADD(MILLISECOND, -3, DATEADD(DAY, 1, CAST(EOMONTH(GETDATE()) AS DATETIME2))) AS EndOfMonthEndOfDay;
You could do the following to keep the current time component:
SELECT DATEADD(DAY, DATEDIFF(DAY, CAST(GETDATE() AS DATE), EOMONTH(GETDATE())), GETDATE()) AS EndOfMonthWithOriginalTime;
Or this:
SELECT CAST(CAST(EOMONTH(GETDATE()) AS VARCHAR(10)) + ' ' + CAST(CAST(GETDATE() AS TIME) AS VARCHAR(12)) AS DATETIME) AS EndOfMonthWithCurrentTime;
Error Handling and Edge Cases
EOMONTH() is pretty robust, but there are a few things to watch out for. If you pass a NULL value as the start date, the function returns NULL:
SELECT EOMONTH(NULL) AS Result;
This is standard SQL Server behavior, but it’s worth keeping in mind when you’re working with columns that might contain NULL values.
The month offset parameter also has limits. While you can use large positive or negative numbers, the resulting date must fall within SQL Server’s supported date range. If you try to calculate a date that would overflow this range, you’ll get an error.
Combining EOMONTH() for Other Uses
EOMONTH() plays well with other SQL Server date functions, letting you build more complex date calculations. For instance, you can combine it with DATEDIFF() to find out how many days remain in the current month:
SELECT DATEDIFF(DAY, GETDATE(), EOMONTH(GETDATE())) AS DaysLeftInMonth;
Or use it with various date part functions to create more sophisticated groupings:
SELECT
YEAR(OrderDate) AS OrderYear,
MONTH(OrderDate) AS OrderMonth,
EOMONTH(OrderDate) AS MonthEnd,
COUNT(*) AS OrderCount
FROM Orders
GROUP BY YEAR(OrderDate), MONTH(OrderDate), EOMONTH(OrderDate);
Performance Considerations
EOMONTH() is a deterministic function, meaning it always returns the same result for the same input. This makes it eligible for use in computed columns and indexes. If you frequently filter or group by month-end dates, you could create a computed column using EOMONTH() and even index it for better performance:
ALTER TABLE Orders
ADD MonthEnd AS EOMONTH(OrderDate) PERSISTED;
CREATE INDEX IX_Orders_MonthEnd ON Orders(MonthEnd);
The PERSISTED keyword tells SQL Server to physically store the computed value, which can speed up queries that reference this column.
Comparing with Alternative Approaches
Before EOMONTH() was introduced, workarounds would be needed to achieve the same result. A common pattern looked something like this:
SELECT DATEADD(DAY, -1, DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) + 1, 0)) AS LastDayOfMonth;
This works, but it’s harder to read and more prone to errors. EOMONTH() makes your code cleaner and more maintainable. If you’re maintaining older SQL Server code (pre-2012), you might encounter these patterns, but for any modern SQL Server instance, EOMONTH() is the way to go.
Dashboard Example with CTE
Suppose you’re building a dashboard that shows monthly sales trends and needs to highlight which months met their sales targets. The following example uses EOMONTH() with a common table expression (CTE) to achieve this:
WITH MonthlySales AS (
SELECT
EOMONTH(OrderDate) AS MonthEnd,
SUM(OrderAmount) AS TotalSales
FROM Orders
WHERE OrderDate >= DATEADD(MONTH, -12, GETDATE())
GROUP BY EOMONTH(OrderDate)
)
SELECT
MonthEnd,
TotalSales,
CASE
WHEN TotalSales >= 15000 THEN 'Target Met'
ELSE 'Below Target'
END AS TargetStatus
FROM MonthlySales
ORDER BY MonthEnd DESC;
This query uses EOMONTH() to group sales by month, looks at the last 12 months of data, and evaluates performance against a target.
Wrapping Up
SQL Server’s EOMONTH() function proves its worth in daily database work by handling month-end calculations cleanly and reliably. It removes the guesswork from calendar arithmetic and keeps your queries readable when you revisit them months later.
The month offset parameter extends its usefulness beyond simple lookups, letting you build queries that calculate billing schedules, compare month-over-month trends, or filter historical data without wrestling with complex date math.
For anyone working with time-series data, financial records, or periodic reporting in SQL Server 2012 or later, EOMONTH() handles a common problem efficiently enough that you’ll find yourself reaching for it regularly.