-1

One of my clients defines (for strange financial reasons) a financial month as a period of time that begins the Wednesday immediately after the last Tuesday of a Month (inclusive) and lasts until the last tuesday of the following month (inclusive).

I need to find the start of the last and the current financial month.

Some examples:

if today is September 23rd 2015 i need to get July 29th and August 26th because the current financial month goes from August 26th to September 29th.

If today is September 30th 2015 I need to get August 26th to September 30th.

I have different clients with different definitions and this means that some of them are using Wednesday and others are using Monday so i need this day to be a parameter, like Monday = 1 and Wednesday = 3. I call it FDOM, FirstDayOfMonth.

My work so far focused on using the formulas i found around with first and last days of current and last month, modified to take into account FDOM. I managed to get last Wednesday of Last Month but this sometimes is not correct because I am considering a day of the month that belongs to a solar month but also to the next financial month, like September 30th belongs to solar September but to Financial October, as financial October begins September 30th.

DECLARE @BASE AS DateTime = '19000101 00:00'

DECLARE @FDOM AS INT = 3 --Wednesday

DECLARE @Datevalue AS DATE = GETDATE()

SET DATEFIRST @FDOM

select DATEADD(D,1-(DATEPART(dw,DATEADD(D,-1,DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @Datevalue) , @BASE)))),DATEADD(D,-1,DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @Datevalue) , @BASE)))

This gives me the first wednesday after the last tuesday of last month and this would be correct from September 1st to September 29th (it gives August 26th) as "the beginning of the current financial month". But it would be wrong on September 30th as it should give September 30th and also wrong from August 26th till the end of August as it should give August 26th but instead gives July 29th.

Johannes Wentu
  • 931
  • 1
  • 14
  • 28
  • So financial April 2015 was April 1st until April 28th (but you'd want to find the dates April 1st and April 29th)? – Damien_The_Unbeliever Sep 23 '15 at 08:14
  • If today is from April 29th to May 26 (both inclusive) I need to get April 1st-April 29th, yes – Johannes Wentu Sep 23 '15 at 08:29
  • In other words: I am creating a subscription for SSRS and when i run the report i need it to be about the last complete financial month. So If I run it from April 29th to May26th inclusive then the last complete financial month should be Financial April, that goes from April 1st inclusive to April 29th EXclusive – Johannes Wentu Sep 23 '15 at 08:34
  • Anyways, don't focus on the fact that i talked about SSRS. The formula i am looking for should be general purpose and i should be able to use it anywhere, this is why i am forcing myself to AVOID CTE and custom functions, because i dont know if i'll be always allowed to use them. I think a CASE should be enough to handle the fact that *today* is part of a solar month and at the same time part of the NEXT financial month – Johannes Wentu Sep 23 '15 at 08:39
  • You meant "August 23rd 2015" instead of "September 23rd 2015", right? – Rubens Farias Sep 23 '15 at 09:18
  • I did mean September. – Johannes Wentu Sep 23 '15 at 11:16
  • @JohannesWentu I have a similar issue. Did any of the below answers resolve your issue? If so, none are marked as the accepted answer. What say you? – STLDev Nov 14 '16 at 20:30
  • @STLDeveloper: yeah sorry you are right. Since it was my answer the one that solved my issue I didnt think about marking it as an answer. I do it now. thx. – Johannes Wentu Nov 15 '16 at 06:46
  • Great! Thank you! – STLDev Nov 16 '16 at 17:59

4 Answers4

0

I think this answers your requirements. Its quite long but hopefully, by breaking things out and naming things, I'm making it clear how we get to the final answer, and so if it's not quite right, it can be adapted:

declare @FDOM int
set @FDOM = 3 --Wednesday. 0 = Sunday, 6 = Saturday

declare @KnownDay datetime
set @KnownDay = DATEADD(day,@FDOM - 1,'20150301') --Offset from a "known good" Sunday to the day before FDOM
declare @EOLastDec datetime
set @EOLastDec = DATEADD(year,DATEDIFF(year,'20010101',GETDATE()),'20001231')
declare @Today datetime
set @Today = DATEADD(day,DATEDIFF(day,0,GETDATE()),0) --You can change this to test other key dates

;With Numbers(n) as (--If you have a numbers table, you can skip this CTE
    select ROW_NUMBER() OVER (ORDER BY so1.object_id) - 1
    from sys.objects so1 cross join sys.objects so2
), LastOfMonths as (
    select DATEADD(month,n,@EOLastDec) as LOM
    from Numbers
    where n between 0 and 13
), LastImportant as (
    select DATEADD(day,-n,LOM) as EOFMonth
    from LastOfMonths cross join Numbers
    where n between 0 and 6 and
    DATEPART(weekday,DATEADD(day,-n,LOM)) = DATEPART(weekday,@KnownDay)
)
select DATEADD(day,1,li0.EOFMonth) as StartOfMonth,DATEADD(day,1,li1.EOFMonth) as EndOfMonth
from
    LastImportant li1
        cross join
    LastImportant li2
        left join
    LastImportant li1_anti
        on
            li1.EOFMonth < li1_anti.EOFMonth and
            li1_anti.EOFMonth <= @Today
        left join
    LastImportant li2_anti
        on
            li2.EOFMonth > li2_anti.EOFMonth and
            li2_anti.EOFMonth >= @Today
        inner join
    LastImportant li0
        on
            li0.EOFMonth < li1.EOFMonth
        left join
    LastImportant li0_anti
        on
            li0_anti.EOFMonth < li1.EOFMonth and
            li0.EOFMonth < li0_anti.EOFMonth
where
    li1.EOFMonth <= @Today and
    li2.EOFMonth >= @Today and
    li1_anti.EOFMonth is null and
    li2_anti.EOFMonth is null and
    li0_anti.EOFMonth is null

Hopefully, the CTEs are reasonably explanatory. We generate a numbers table, and then we calculate the last day of each month, and from there, we step up to 6 days backwards to locate a day of the right type (i.e. a Tuesday, if @FDOM is 3)

I originally had a simpler final query using just li1 and li2 (and li1_anti and li2_anti), but realised that the query was just finding the current financial month - so I've added another couple of joins (using li0 and li0_anti) to find the start of the previous financial month.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
0

Try this. You can use EOMONTH function to get the end of month on Sql Server 2012 or above.

Click to see the fiddle demo.

DECLARE @date DATETIME = GETDATE()
DECLARE @LastMonthEnd DATETIME = DATEADD(day, -1, DATEADD(month, DATEDIFF(month, 0, @date), 0)) 
DECLARE @CurrentMonthEnd DATETIME =  DATEADD(day, -1, DATEADD(month, DATEDIFF(month, 0, @date) + 1, 0)) 


SET DATEFIRST 1

;WITH CTE1 AS
(
    SELECT 1 number, DATEPART(WEEKDAY, @LastMonthEnd) FirstDay, 
                     DATEPART(WEEKDAY, @CurrentMonthEnd) LastDay    
    UNION ALL

    SELECT 1+number,  DATEPART(WEEKDAY, DATEADD(DAY, -number, @LastMonthEnd)),  
                      DATEPART(WEEKDAY, DATEADD(DAY, -number, @CurrentMonthEnd))
    FROM CTE1 
    WHERE number < 7
)
SELECT DATEADD(DAY, -(SELECT Number FROM CTE1 WHERE FirstDay = 3), @LastMonthEnd) StartDate,
       DATEADD(DAY, -(SELECT Number FROM CTE1 WHERE LastDay = 3), @CurrentMonthEnd) EndDate
Kaf
  • 33,101
  • 7
  • 58
  • 78
0

Calculate the start date from previous month and the last day from current month and used a CTE to generate all dates between them. Later, get the MAX weekday from both months.

DECLARE @CurrentDate    DATE = '2015-08-23'
DECLARE @StartDate      DATE,
        @EndDate        DATE,
        @MonthEnd       INT = 3

--  Get the first day from previous month and last day from current month
SELECT  @StartDate      = DATEADD(MONTH , DATEDIFF(MONTH, 0, @CurrentDate)-1, 0),
        @EndDate        = DATEADD(SECOND,-1, 
                          DATEADD(MONTH , DATEDIFF(MONTH, 0, @CurrentDate)+1,0))

;WITH   Calendar    AS
(       -- Generate all dates between @StartDate and @EndDate
        SELECT  @StartDate  [Date]
        UNION   ALL
        SELECT  DATEADD(D, +1, Calendar.[Date])
        FROM    Calendar
        WHERE   Calendar.[Date] < @EndDate
)
SELECT  DATEADD(DAY, +1, MAX(StartDate.[Date])) StartDate,
        DATEADD(DAY, +1, MAX(EndDate  .[Date])) EndDate
FROM    Calendar    StartDate,
        Calendar    EndDate
WHERE   -- Get the max weekday from previous month
        DATEPART(MONTH  , StartDate.[Date]) = DATEPART(MONTH, @StartDate) AND
        DATEPART(WEEKDAY, StartDate.[Date]) = @MonthEnd AND
        -- Get the max weekday from current month
        DATEPART(MONTH  , EndDate  .[Date]) = DATEPART(MONTH, @EndDate) AND
        DATEPART(WEEKDAY, EndDate  .[Date]) = @MonthEnd
Rubens Farias
  • 57,174
  • 8
  • 131
  • 162
0

After a lot of effort I managed to find an expression that DOES NOT use CTE as i am not sure i can't use CTE in all the places i will have to employ this.

So basically first i understand with a CASE if the date i am considering is before or after the last Wednesday of the Month it belongs to. Then i return the last wednseday of two and of one month ago OR the last wednesday of one month ago and of this month. This works also changing FDOM and i tested it for several months of this year. It seems to always work. Probably the use of EOMonth would shorten it but i have to verify i can use it in my server.

I am sorry i just specified as a requirement that i can't use a CTE only in the comments but thank you for your help

DECLARE @datevalue AS Datetime = getdate()
DECLARE @BASE AS DateTime = '19000101 00:00'
DECLARE @FDOM AS INT = 3 --1 is for Monday
SET DATEFIRST @FDOM

SELECT CASE WHEN (@datevalue < DATEADD(D, 1-(DATEPART(dw,DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) + 1, @BASE))),DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) + 1, @BASE)))
        THEN(DATEADD(D, 1-(DATEPART(dw,DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) - 1, @BASE))),DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) - 1, @BASE)))
        ELSE(DATEADD(D, 1-(DATEPART(dw,DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) - 0, @BASE))),DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) - 0, @BASE)))
        END AS [Start of Last Financial Month]
,CASE WHEN (@datevalue < DATEADD(D, 1-(DATEPART(dw,DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) + 1, @BASE))),DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) + 1, @BASE)))
        THEN(DATEADD(D, 1-(DATEPART(dw,DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) + 0, @BASE))),DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) + 0, @BASE)))
        ELSE(DATEADD(D, 1-(DATEPART(dw,DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) + 1, @BASE))),DATEADD(MONTH, DATEDIFF(MONTH, @BASE, @datevalue) + 1, @BASE)))
        END AS [Start of Current Financial Month]
Johannes Wentu
  • 931
  • 1
  • 14
  • 28