Click here to monitor SSC
SQLServerCentral is supported by Red Gate Software Ltd.
 
Log in  ::  Register  ::  Not logged in
 
 
 
        
Home       Members    Calendar    Who's On


Add to briefcase ««1234»»»

4-4-5 Calendar Functions, Part 1 Expand / Collapse
Author
Message
Posted Monday, September 28, 2009 11:51 PM


SSC-Dedicated

SSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-Dedicated

Group: General Forum Members
Last Login: Yesterday @ 9:22 PM
Points: 37,990, Visits: 34,893
wldhrs (9/28/2009)
thomasrawley, while i detest your naming conventions, how many compilers care if the name is fscl_yr, how many compilers care if the name is [Fiscal Year] ... go on bite me Jeff,


Heh... bite yourself... I don't care what you call your stuff. I'd much rather just see FiscalYear instead of the abbreviations or the report formatted column name, but whatever.

Jeff, are you actually against calendar tables or are you just playing the game?

Oh heck no... not against calendar tables at all. I just see all those folks talking about them but no code to help others out.

Editted to fix missing ]'s, Doh.

Heh... see?


--Jeff Moden
"RBAR is pronounced "ree-bar" and is a "Modenism" for "Row-By-Agonizing-Row".

First step towards the paradigm shift of writing Set Based code:
Stop thinking about what you want to do to a row... think, instead, of what you want to do to a column."

(play on words) "Just because you CAN do something in T-SQL, doesn't mean you SHOULDN'T." --22 Aug 2013

Helpful Links:
How to post code problems
How to post performance problems
Post #794917
Posted Tuesday, September 29, 2009 3:20 AM


Valued Member

Valued MemberValued MemberValued MemberValued MemberValued MemberValued MemberValued MemberValued Member

Group: General Forum Members
Last Login: Monday, June 8, 2015 3:17 AM
Points: 54, Visits: 130
Here's the approach that I use - with a bit of code - and that has served me well in a number of UK-based organisations.

For background, UK fiscal years can start almost anywhere in the "real" calendar - my current organisation begins and ends theirs on the 4th Sunday in September (so the current year, just begun - 2009/10 - is a "53" week one). Quarter/Period patterns are generally 4-4-5, except that in a 53-week year, one of them will be either a 4-5-5 or a 5-4-5 one. The problem here is that the determination of which quarter is to get that treatment, and what the treatment is, is determined by the executives relatively close to the time so it is not practical to load the calander table too far ahead!

The approach I use has a "seed" table with 371 (i.e. 53 weeks worth) or days in it. This has five columns:

pkintSeedDay - day of the fiscal year (1 thru 371)
intSeedPeriod - corresponding period for that day if a 52 week year
intSeedWeekOfPeriod - corresponding week of the period for that day if a 52 week year
intSeedPeriod53 - corresponding period for that day if a 53 week year
intSeedWeekOfPeriod53 - corresponding week of the period for that day if a 53 week year

You can set this up manually or write a bit of code to do so - but its done once only.

The main calendar table has eleven columns (you can remove any you don't want or add any that would be of value) as follows:

pkdatDate - the actual date
intDayOfFY - the day within the fiscal year - 1 to 364 or 371
intWeekOfPeriod - the week within the period - 1 to 4 or 5
intPeriodOfFY - the relevent period - 1 to 12
intQuarterofFY - the relevent quarter - 1 to 4
intFYID - a value that can be used to get the textual name of the financial year from a lookup table (eg fiscal year "9" may lookup the text "2009/2010") for reporting
intWeekOfFY - the week number within the fiscal year - 1 to 52 or 53
intDayOfWeek - the day within the company week - 1 to 7 (see note below)
intWeeksAgo - this and the next two columns are updated daily (see notes below)
intDaysAgo -
intPeriodsAgo -

Note regarding intDayOfWeek: this is stored because use of DATEPART to get the same thing is langauage/locale dependent whereas the company has its own rigid interpretation of when a week starts and ends.
Note regarding intDaysAgo: this is set to a zero if this row reflects todays date, with positive numbers representing days already past and negative numbers representing days in the future.
Note regarding intWeeksAgo: this is set to a zero if this row is in the same fiscal week as todays date, with positive numbers representing weeks already past and negative numbers representing weeks in the future.
Note regarding intPeriodsAgo: this is set to a zero if this row is in the same fiscal period as todays date, with positive numbers representing periods already past and negative numbers representing periods in the future.

Each year, the following code is executed (after making any required changes) to load a new years worth of rows into the calandar table using the feeder table as a base. If it is a 53-week year (and the "extra" week has moved since the last time a 53-week year was loaded) then the feeder table will need to be realigned first using the code at the foot of this article.

USE EXTRACT;
DECLARE @startdate datetime, @fy int, @fyname char(7), @days int
--
-- Set the parameters below to the required values
--
-- SET @startdate = '2007-09-30'
SET @startdate = (SELECT MAX(pkdatDate)+1 FROM tblFinancialCalendar)
-- Comment out one of the above two lines:
-- use the first if you want to set a fixed start date and use the second if you want to append a new year to the end of the existing table
SET @fy = 9
SET @fyname = 'FY09/10'
SET @days = 371
-- @days must be set to 364 for a 52 week year, or set to 371 for a 53 week year
-- ( but check that the additional week is in the correct period in the
-- CalendarFeeder table first or run prcCalendarFeeder53Setup to reset it )
--
-- Load the new year into the main table from the feeder table
--
INSERT INTO tblFinancialCalendar
SELECT
@startdate-1+pkintSeedDay, -- pkdatDate
pkintSeedDay, -- intDayOfFY
CASE WHEN @days=364 THEN intSeedWeekOfPeriod ELSE intSeedWeekOfPeriod53 END, -- intWeeekOfPeriod
CASE WHEN @days=364 THEN intSeedPeriod ELSE intSeedPeriod53 END, -- intPeriodOfFY
CASE
WHEN intSeedPeriod>= 1 and intSeedPeriod<=3 THEN 1
WHEN intSeedPeriod>= 4 and intSeedPeriod<=6 THEN 2
WHEN intSeedPeriod>= 7 and intSeedPeriod<=9 THEN 3
WHEN intSeedPeriod>= 10 and intSeedPeriod<=12 THEN 4
END, -- intQuarterOfFY
@fy, -- intFYID
FLOOR((pkintSeedDay-1)/7)+1, -- intWeekOfFY
NULL, -- intDayOfWeek (set below)
NULL, -- intWeeksAgo (reset daily)
NULL, -- intDaysAgo (reset daily)
NULL -- intPeriodsAgo (reset daily)
FROM tblCalendarFeeder
WHERE pkintSeedDay<=@days;
--
-- Update the calandar to set the "day of the week" values
-- (These are used to prevent issues caused by the DATEPART(dw,xxx) function which gives variable outputs dependent on language
-- and other settings that may be in effect at a per-user level)
--
SET DATEFIRST 7; -- Our financial weeks run from Sunday to Saturday
UPDATE tblFinancialCalendar SET intDayOfWeek=DATEPART(dw,pkdatDate);
--
-- Add or update new year into the Financial Year Names table
--
DELETE FROM tblCalendarFinancialYearNames WHERE pkintFYID=@fy;
INSERT INTO tblCalendarFinancialYearNames
SELECT
@fy,
@fyname;


The table can now be joined to any date elsewhere in the database to obtain fiscal period data.

Each night the "DaysAgo", "WeeksAgo" and "PeriodsAgo" columns are recalculated using the following code:.

UPDATE tblFinancialCalendar SET 
intWeeksAgo=DATEDIFF(wk,pkdatDate,GETDATE());
UPDATE tblFinancialCalendar SET
intDaysAgo=DATEDIFF(dy,pkdatDate,GETDATE());
UPDATE tblFinancialCalendar SET
intPeriodsAgo=(SELECT COUNT(DISTINCT intFYID*100+intPeriodOfFY)-1
FROM tblFinancialCalendar b
WHERE pkdatDate BETWEEN tblFinancialCalendar.pkdatDate AND GETDATE())
WHERE tblFinancialCalendar.pkdatDate<=GETDATE();
UPDATE tblFinancialCalendar SET
intPeriodsAgo=(SELECT -COUNT(DISTINCT intFYID*100+intPeriodOfFY)+1
FROM tblFinancialCalendar b
WHERE pkdatDate BETWEEN GETDATE() AND tblFinancialCalendar.pkdatDate)
WHERE tblFinancialCalendar.pkdatDate>GETDATE();

These columns can then be used to select data from tables to which the calandar has been joined, for reporting perposes. For example, you could select all data from the previous period by adding a "WHERE intPeriodsAgo=1" clause.

Finally, here is the code that I use to reset the 53rd week if it has moved since the last time a 53-week year was added:

DECLARE @period int,@cut int;
--
-- Set the following parameter before running
--
SET @period=11;
-- Enter the period which is to have the extra 5th week (should be one of 1,2,4,5,7,8,10 or 11)
SET @cut=(SELECT MAX(pkintSeedDay) FROM tblCalendarFeeder WHERE intSeedPeriod=@period);
CREATE TABLE tblCalendarFeeder2
(pkintSeedDay INT NOT NULL PRIMARY KEY,intSeedPeriod INT NOT NULL,intSeedWeekOfPeriod INT NOT NULL);
UPDATE tblCalendarFeeder SET intSeedPeriod53=NULL,intSeedWeekOfPeriod53=NULL;
INSERT INTO tblCalendarFeeder2 SELECT pkintSeedDay,intSeedPeriod,intSeedWeekOfPeriod FROM tblCalendarFeeder;
UPDATE tblCalendarFeeder SET
intSeedPeriod53=(select intSeedPeriod from tblCalendarFeeder2 b
where tblCalendarFeeder.pkintSeedDay=b.pkintSeedDay and tblCalendarFeeder.pkintSeedDay<=@cut),
intSeedWeekOfPeriod53=(select intSeedWeekOfPeriod from tblCalendarFeeder2 b
where tblCalendarFeeder.pkintSeedDay=b.pkintSeedDay and tblCalendarFeeder.pkintSeedDay<=@cut)
WHERE tblCalendarFeeder.pkintSeedDay<=@cut;
UPDATE tblCalendarFeeder SET
intSeedPeriod53=(select intSeedPeriod from tblCalendarFeeder2 b
where tblCalendarFeeder.pkintSeedDay=b.pkintSeedDay+7 and tblCalendarFeeder.pkintSeedDay>@cut+7),
intSeedWeekOfPeriod53=(select intSeedWeekOfPeriod from tblCalendarFeeder2 b
where tblCalendarFeeder.pkintSeedDay=b.pkintSeedDay+7 and tblCalendarFeeder.pkintSeedDay>@cut+7)
WHERE tblCalendarFeeder.pkintSeedDay>@cut+7;
UPDATE tblCalendarFeeder SET intSeedPeriod53=@period,intSeedWeekOfPeriod53=5 WHERE intSeedPeriod53 IS NULL;
DROP TABLE tblCalendarFeeder2;

Post #794978
Posted Tuesday, September 29, 2009 7:30 AM
Valued Member

Valued MemberValued MemberValued MemberValued MemberValued MemberValued MemberValued MemberValued Member

Group: General Forum Members
Last Login: Yesterday @ 5:18 AM
Points: 62, Visits: 1,129
Jeff Moden (9/28/2009)
Heh... I see 3 recommendations to build a Calendar table and two that claim they've used one, yet no one has offered up any of their own code to show they have ever done so. Like the tag line from the movie goes... Show Me the Money!


Jeff - did you count my rambling on about week numbers as one of those recommendations ?

if so, you're making me feel all guilty and I'll have to go off and try and find a copy of that database in an archive somewhere.....

/Ryan
Post #795117
Posted Tuesday, September 29, 2009 9:17 AM
SSC Rookie

SSC RookieSSC RookieSSC RookieSSC RookieSSC RookieSSC RookieSSC RookieSSC Rookie

Group: General Forum Members
Last Login: 2 days ago @ 8:18 PM
Points: 39, Visits: 297
I've seen several replies about using a table to store all of the data that I might need. I have explored this as an option and I definitely think it is a possibility. As Joe Celko pointed out my approach is not SET based and SQL server does like its sets. I try to use them when I can and if you can implement this idea with sets I'd say that's a great way to go. I'll probably even do a table implementation myself.

One thing missing from the table suggestions is the idea that the starting day of the week could change. The starting day could be Sunday for one company and Monday for another company. You could still build a table to take this into account. Let me know if I'm missing something and the tables do take this into account.

The comment from Ryan Price is also interesting. I'd like to see some performance metrics on date range joins.

In the end, I came up with a little function that worked for me and wanted to share it since I had a lot of trouble finding much out there. I'm going to share my function for getting a Period (like a month), which is also procedural based. For a table implementation maybe my functions could be modified to populate a table. Sounds like I or someone could even do a comparison article on function based, set based, and various join methods. That could be fun.

Thanks everyone for your comments. Keep them coming.



Post #795222
Posted Tuesday, September 29, 2009 10:02 AM


SSC-Dedicated

SSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-Dedicated

Group: General Forum Members
Last Login: Yesterday @ 9:22 PM
Points: 37,990, Visits: 34,893
Ryan C. Price (9/29/2009)
Jeff Moden (9/28/2009)
Heh... I see 3 recommendations to build a Calendar table and two that claim they've used one, yet no one has offered up any of their own code to show they have ever done so. Like the tag line from the movie goes... Show Me the Money!


Jeff - did you count my rambling on about week numbers as one of those recommendations ?

if so, you're making me feel all guilty and I'll have to go off and try and find a copy of that database in an archive somewhere.....

/Ryan


Heh... I guess my comment worked. Good folks are coughing up some good stuff. Thank you one and all.


--Jeff Moden
"RBAR is pronounced "ree-bar" and is a "Modenism" for "Row-By-Agonizing-Row".

First step towards the paradigm shift of writing Set Based code:
Stop thinking about what you want to do to a row... think, instead, of what you want to do to a column."

(play on words) "Just because you CAN do something in T-SQL, doesn't mean you SHOULDN'T." --22 Aug 2013

Helpful Links:
How to post code problems
How to post performance problems
Post #795266
Posted Tuesday, September 29, 2009 10:04 AM


SSC-Dedicated

SSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-DedicatedSSC-Dedicated

Group: General Forum Members
Last Login: Yesterday @ 9:22 PM
Points: 37,990, Visits: 34,893
andyscott (9/29/2009)
Here's the approach that I use - with a bit of code - and that has served me well in a number of UK-based organisations.


Very cool. That's a heck of an explanation. Thanks for taking the time.


--Jeff Moden
"RBAR is pronounced "ree-bar" and is a "Modenism" for "Row-By-Agonizing-Row".

First step towards the paradigm shift of writing Set Based code:
Stop thinking about what you want to do to a row... think, instead, of what you want to do to a column."

(play on words) "Just because you CAN do something in T-SQL, doesn't mean you SHOULDN'T." --22 Aug 2013

Helpful Links:
How to post code problems
How to post performance problems
Post #795267
Posted Tuesday, September 29, 2009 10:38 AM
SSC-Addicted

SSC-AddictedSSC-AddictedSSC-AddictedSSC-AddictedSSC-AddictedSSC-AddictedSSC-AddictedSSC-Addicted

Group: General Forum Members
Last Login: Wednesday, November 17, 2010 3:38 AM
Points: 445, Visits: 82
How common is this 4-4-5 (or variant) thinking in modern accounting systems? This is the first I've heard of it and I've DBA'ed behind three different fairly popular accounting systems, none of which incorporated this possibility. The assumption was always that a period was monthly.

Then my second question is that every 5 or 6 years you'll need an extra week thrown in somewhere. Do the accountants just make a 4-5-5 in a fiscal quarter?



Post #795291
Posted Tuesday, September 29, 2009 11:37 AM
SSC Rookie

SSC RookieSSC RookieSSC RookieSSC RookieSSC RookieSSC RookieSSC RookieSSC Rookie

Group: General Forum Members
Last Login: 2 days ago @ 8:18 PM
Points: 39, Visits: 297
drnetwork (9/29/2009)
How common is this 4-4-5 (or variant) thinking in modern accounting systems? This is the first I've heard of it and I've DBA'ed behind three different fairly popular accounting systems, none of which incorporated this possibility. The assumption was always that a period was monthly.

Then my second question is that every 5 or 6 years you'll need an extra week thrown in somewhere. Do the accountants just make a 4-5-5 in a fiscal quarter?


I have no idea how common it is but we had been running our application for about 10 years before it came up so we had to come up with a solution. And I think, non-accountant that I am, that every few years there is an extra week thrown in. I mention this in my article and andyscott mentions it, too.

A further question I have on a table implementation (yes, I think it's a great idea but I'm pondering things) is what about shifting the start date of the year. Meaning, if you build the table where year 2008 starts on 1/6/2008 then the table's data is fine. But what happens if the accountants decide that the year should start on the first Sunday after Sept 1st? Then another accountant says it should start on the first Tuesday after July 1st? I'm thinking you'd need more key values in the table but I'm curious if anyone has any great implementation ideas.

Cliff



Post #795340
Posted Wednesday, September 30, 2009 3:26 AM


Valued Member

Valued MemberValued MemberValued MemberValued MemberValued MemberValued MemberValued MemberValued Member

Group: General Forum Members
Last Login: Monday, June 8, 2015 3:17 AM
Points: 54, Visits: 130
corder (9/29/2009)[hr
I have no idea how common it is but we had been running our application for about 10 years before it came up so we had to come up with a solution. And I think, non-accountant that I am, that every few years there is an extra week thrown in. I mention this in my article and andyscott mentions it, too.

A further question I have on a table implementation (yes, I think it's a great idea but I'm pondering things) is what about shifting the start date of the year. Meaning, if you build the table where year 2008 starts on 1/6/2008 then the table's data is fine. But what happens if the accountants decide that the year should start on the first Sunday after Sept 1st? Then another accountant says it should start on the first Tuesday after July 1st? I'm thinking you'd need more key values in the table but I'm curious if anyone has any great implementation ideas.


I think it depends on the nature of your company's business - if business is very geared to a weekly cycle, then the imposition of the "calendar month" as a "period" is too inflexible. Hence - if your business still wishes to report on a pseudo-monthly cycle, ie twelve times in a year - the need for fiscal periods each of an integral number of weeks. But the definition of the year itself - in terms of when it starts and ends and, in some instances, what any interim points are - is (at least for a UK-based, publically listed company) strictly controlled, since it is a requirement of its registration on the stock market. So its not just down to the accountants to choose!

Irrespective of whether you 4-4-5 or any other method of weekly-based reporting, there will be a requirement to insert a 53rd week from time to time. This comes about because of (a) 52 weeks of 7 days is one day short of a normal year of 365 days and (b) leap years add a further day every (nearly) four years too. So after 6 years, you'll have had an extra 6 days from (a) and, likely, an extra 1 day from (b), and you'll need to cater for a 53rd week. Depending on timing (ie where the leap years are - or aren't!) this may come after the fifth year or may be delayed until the seventh - but every sixth year is what you should plan for. If your business still wants to report over 12 periods, then one of the existing periods will need to have the extra week added.

But even if you do have to have two (or more) sets of cycles (for instance, if you are doing reporting for business with different year start/end dates) its easy to extend a calendar table to contain multiple sets of fiscal period data by having distinct sets of columns for each set of cycles.
Post #795606
Posted Monday, January 11, 2010 7:31 PM
SSC-Enthusiastic

SSC-EnthusiasticSSC-EnthusiasticSSC-EnthusiasticSSC-EnthusiasticSSC-EnthusiasticSSC-EnthusiasticSSC-EnthusiasticSSC-Enthusiastic

Group: General Forum Members
Last Login: Friday, December 19, 2014 6:43 AM
Points: 102, Visits: 349
I know, old topic but better late than never.

I had to build calendar tables a while ago. Recently I was force to revisit this because of a little trick the accounting department played on us. We use a 4-4-5 calendar but with certain specifics.

1: Fiscal years always start on January 1st and end on December 31.
2: Our periods end on Fridays
3: For 2010 they've decided to push the first period up to January 29 instead of the 22nd because they didn't want the last period to be 6 weeks long.

Because of this I had to rewrite the code that generates my calendar tables. Here's how I do it. I use 2 tables, FiscalCalendar to keep the periods for every day of the year ans FiscalPeriods to keep.......fiscal periods . To speed up any code that will use them I also created a few indexes.

Create Table FiscalCalendar
(
Period char(6),
CalDate Datetime,
PeriodYear SmallInt,
PeriodMonth SmallInt
)
Create Index IX_FiscalCalendar_Date on FiscalCalendar (CalDate)
Create Index IX_FiscalCalendar_Period on FiscalCalendar (Period)

Create Table FiscalPeriods
(
PeriodMonth smallint,
PeriodStartDate Datetime,
PeriodEndDate Datetime,
Period char(6)
)
Create Index IX_FiscalPeriods_Period on FiscalPeriods (Period)
Create Index IX_FIscalPeriods_Month on FiscalPeriods (PeriodMonth)
Create Index IX_FiscalPeriods_StartDate on FiscalPeriods (PeriodStartDate)

In the code that populates the tables I use a function that I wrote a long time ago. I'm sure it could be rewritten to be much nicer but it's simple and it served me well for a long time so there's no point in rewriting it .........if it's not broken............

ZeroFill receives and integer and returns a string of the requested length padded with leading zeroes. It is very useful to convert year 2010 period 1 into 201001.

Create Function ZeroFill 
(
@myNum int,
@myLength int
)
Returns char(10)
As
Begin
declare @StrValue char(20)
declare @RetValue char(20)
set @StrValue = cast(@myNum as char(20))
set @RetValue = replicate('0',@MyLength-len(@strValue)) + ltrim(rtrim(@StrValue))
return ltrim(rtrim(@Retvalue))
end

And here's the procedure itself. It takes the year and the day of the first month end as parameters.


Create Procedure CreateFiscalCalendarYear
(
@Year int,
@Day int
)
As

Declare @YearStartdate datetime
Declare @YearEndDate Datetime
Declare @FirstClosingDate Datetime
Declare @NumDays int
Declare @Weeks01 int
Declare @Weeks02 int
Declare @Weeks03 int
Declare @Weeks04 int
Declare @Weeks05 int
Declare @Weeks06 int
Declare @Weeks07 int
Declare @Weeks08 int
Declare @Weeks09 int
Declare @Weeks10 int
Declare @Weeks11 int
Declare @Weeks12 int

-- The 12 @Weeks variables keep the week numbers of each month end.

Set @FirstClosingDate = cast(@Year as char(04)) + '-01-' + dbo.zerofill(@Day,2)

Set @YearStartDate = cast(@Year -1 as char(4)) + '-12-31'
Set @YearEndDate = cast(@Year as char(4)) + '-12-31'

-- Note that I set my @YearStartDate variable to the last day of the previous year.
-- That's because it's used to calculate the number of days of my year using DateX - DateY
-- and I want it include January 1st. Not very nice but efficient and that calculation is
-- necessary because we need to know if we're on a 365 days year or 366.

-- Get the week of the first month end. This will be either 1 or 2.
-- Then apply the remaining of a 4-4-5 "mask" to get the other week numbers.
Set @Weeks01 = DatePart(Week, @FirstClosingDate)
Set @Weeks02 = @Weeks01 + 4
Set @Weeks03 = @Weeks02 + 5

Set @Weeks04 = @Weeks03 + 4
Set @Weeks05 = @Weeks04 + 4
Set @Weeks06 = @Weeks05 + 5

Set @Weeks07 = @Weeks06 + 4
Set @Weeks08 = @Weeks07 + 4
Set @Weeks09 = @Weeks08 + 5

Set @Weeks10 = @Weeks09 + 4
Set @Weeks11 = @Weeks10 + 4
Set @Weeks12 = @Weeks11 + 5

-- Get the number of days in the year. We will use it to create the
-- right number of records in the FiscalCalendar table.

Set @NumDays = (select DateDiff(day, @YearStartDate,@YearEndDate ))


-- We all make mistakes and sometimes we have to re-run the code. Since our tables
-- may already contain the information for the year we're working with we need to clean
-- it up

Delete from FiscalCalendar where year(CalDate) = @Year
Delete from FiscalPeriods where Year(PeriodStartDate) = @Year


-- and then create our data starting with the FiscalCalendar table.
-- Using a Tally table (Thanks Jeff ;)) we can easily create our @Numdays records.

Insert Into FiscalCalendar
(
CalDate, PeriodYear
)
Select top (@NumDays)
DateAdd(Day, N, @YearStartDate), year(DateAdd(Day, N, @YearStartDate))
From Tally1000 order by N


-- Next I update the period month for the days that mark the end of a period.
-- Using dense_rank and my @Weeks variable I can create a memory table
-- that will hold my 12 end dates.
-- Note that "where datepart(weekday,caldate) = 6 specifies my periods
-- end on Fridays, which is day 6 of the week.
Declare @myTable Table
(
Mydate datetime,
pMonth int
)
insert into @myTable
Select CalDate, dense_rank()
over(partition by year(CalDate) order by CalDate)
from fiscalcalendar where datepart(weekday, CalDate) = 6 and
datepart(week,CalDate) in
(
@Weeks01, @Weeks02, @Weeks03, @Weeks04, @Weeks05, @Weeks06,
@Weeks07, @Weeks08, @Weeks09, @Weeks10, @Weeks11, @Weeks12
)

-- And then write them back to the FiscalCalendar table. The other columns will be
-- populated later.
Update FiscalCalendar
set PeriodMonth = a.pMonth
from @myTable a
where myDate = fiscalcalendar.CalDate

-- Time to get started with the FiscalPeriods table.
-- We start by creating our 12 records. This is pretty easy since
-- we happen to have 12 records in the FiscalCalendar table that
-- have their period end dates.

insert into FiscalPeriods
(
PeriodEndDate
)
Select CalDate
From fiscalcalendar
where PeriodMonth is not null and year(caldate) = @Year
order by Caldate

-- Then we need to update the PeriodStartDate and PeriodEndDate columns.
-- Two of them are easy. We know that the start date of the first period is January 1
-- and that the end date of the last period is December 31.

Update FiscalPeriods
set PeriodStartDate = cast(year(PeriodEndDate) as char) + '-01-01'
where month(PeriodEndDate) = 1 and Year(PeriodEndDate) = @Year

Update FiscalPeriods
set PeriodEndDate = cast(year(PeriodEndDate) as char) + '-12-31'
where month(PeriodEndDate) = 12 and Year(PeriodEndDate) = @Year

-- With those values in the table we can use a sub-query to update each PeriodStartDate
-- with the previous PeriodEndDate + 1

Update FiscalPeriods
set PeriodStartDate =
(
select dateadd(day, 1, max(PeriodEndDate))
from FiscalPeriods a
where a.PeriodEndDate < fiscalPeriods.PeriodEndDate
)
where PeriodStartDate is null and Year(PeriodEndDate) = @Year


-- The FiscalPeriods table require one last update to populate
-- the Period column. Once again I'm using a memory table
-- and dense_rank

declare @MyTable2 Table
(
Period Char(4),
PeriodStartDate datetime,
PeriodEndDate datetime
)

Insert into @MyTable2
select dense_rank()
over(partition by year(PeriodStartDate)
order by PeriodEndDate), PeriodStartDate, PeriodEndDate
From FiscalPeriods
where Year(PeriodStartDate) = @Year

-- With my memory table loaded I only need to delete records for that year
-- in my physical table and reload it.

Delete from FiscalPeriods where Year(PeriodStartDate) = @Year

Insert into FiscalPeriods
(
PeriodMonth, PeriodStartDate, PeriodEndDate, Period
)
Select Period, PeriodStartDate, PeriodEndDate,
cast(year(periodstartdate) as char(4))+ dbo.zerofill(period,2)
from @MyTable2


-- With my FiscalPeriods table complete, I can come back to the FiscalCalendar
-- table and update the empty columns that I didn't handle before from
-- the FiscalPeriods table

-- Period Months
Update FiscalCalendar
set PeriodMonth = a.PeriodMonth
From FiscalPeriods a
where PeriodYear = @Year and caldate between a.PeriodStartDate and
a.PeriodEndDate

-- Period Codes
Update FiscalCalendar
set Period = cast(PeriodYear as char(4)) + dbo.zerofill(PeriodMonth,2)
where PeriodYear = @Year

-- And create the procedure
go

Now I can add a year to my fiscal calendar with a simple procedure call passing it the year and the day of the first month end.

Exec CreateFiscalCalendarYear 2010, 29


My FiscalCalendar table contains an auto-increment integer column defined as Primary Key which I didn't put here. It's used to build relations with other tables to create fiscal calendar dimensions in my cubes.

I also have a number of Fiscal Calendar oriented functions that are used in other processes.

GetShortDate(): Very useful when trying to link a datetime field that contains the time part with the calendar.
Create Function GetShortDate
(
@vDate datetime
)
returns datetime
as
begin
declare @wDate datetime
Set @wDate = CAST(FLOOR(CAST(@vDate AS FLOAT ))AS DATETIME)
Return @wDate
End

Getting the start date of the fiscal period for a given date.
Create Function GetFiscalBOM(@Date Datetime) returns Datetime 
as
Begin
Declare @RetValue datetime
Select @RetValue = PeriodStartDate
from FiscalPeriods
where @Date between PeriodStartDate and PeriodEndDate
Return @RetValue
end

Getting the Period Code (YYYYMM) for a given date
Create Function GetFiscalPeriod(@Date datetime) returns Char(6)
as
Begin
Declare @RetValue char(6)
select @RetValue = period
from FiscalCalendar
where CalDate = dbo.getshortdate(@Date)
Return @RetValue
End


Getting the start date of a given period
Create Function GetPeriodStartdate(@Period char(6))
returns datetime
as
Begin
Declare @RetValue Datetime
Select @RetValue = PeriodStartDate From FiscalPeriods
where Period = @Period
Return @RetValue
End

I'm sure some will criticize my code and come up with much nicer ways of doing this but so far I haven't seen any code on the forum to build a 4-4-5 calendar starting on January 1st, ending on December 31 and with a first month end date different than where 4 weeks puts you.

Hopefully it can help some people. At least it will show Jeff one more coder who actually did it......I even use the Tally table technique that I learned from your article :)
Post #845813
« Prev Topic | Next Topic »

Add to briefcase ««1234»»»

Permissions Expand / Collapse