## 2014-05-26

### Revit Calendar

Hi all. I've been blogging about Revit in Turkish for a time. (revitkutuphanesi.com) And I'll give it a try to do it in English here to interact with more people.

Why not start a blog by giving away something to the community? Here I represent to you the Revit Calendar Family:

D_GEN_CALENDAR.rfa (Revit 2015)

I was working on my LOD 300 Rebar Calculation add-in for Revit. For a week or two I was so concentrated on understanding and solving the problem, when it finished I've felt a little bit blank.
Had to do something Cool and pretty much useless to get over the boredom.

The calendar family has 3 data entry parameters. Day, Month, Year. If the Day parameter is Zero, it will basically show the monthly calendar.

If you enter a nice date (like my Son's b-day) it will add the day of the week to the title and highlight the date.

Although the family has some DRV (driving / correcting) parameters it doesn't cover negative years.
I'm sure it can be added to the DRV formulas but I didn't want to spend more than an a hour on it.
So any parameter "undersized  / oversized" value defaults to:

dayDRV= 0 /  maximum days this month has
monthDRV = 1 / 12
yearDRV =2 / ...

As time passed I can't remember the challenges in the exact order I had, but here they are:

LEAP YEAR (Leap Yes/No Parameter)

Had to figure out if the entered Year is a leap year. After searching the internet for a decent algorithm, luckily I've found this one in wikipedia.

if year is not divisible by 4 then common year
else if year is not divisible by 100 then leap year
else if year is not divisible by 400 then common year
else leap year

which I can rewrite in Revit as:

if(not(yearDRV / 4 = rounddown(yearDRV / 4)), 1 = 0, if(not(yearDRV / 100 = rounddown(yearDRV / 100)), 1 = 1, if(yearDRV / 400 = rounddown(yearDRV / 400), 1 = 1, 1 = 0)))

Revit does not have a modulo operator (finding the remainder of a division) in its syntax but you can use the roundown trick to find mod x = 0.

yearDRV / 4 = rounddown(yearDRV / 4)

If it can be divided to four without a remainder, it should be equal to the rounddown of the same division Right? Rest is adjusting the formula with ifs and nots..

DETERMINATION of THE FIRST DAY

To start the month I had to find out the location of the first day in the table. Again it was lovely to find out a page dedicated to this in wikipedia.

There were a lot of promising methods. First I thought using the "methods with tables" would be so cool but due to the time restrain I had to choose from one of the "Purely Mathematical Algorithms". I've chosen Zeller's Congruence

h is the day of the week (0 = Saturday, 1 = Sunday, 2 = Monday, ...)
q is the day of the month (I'm looking for the FirstDay = 1)
m is the month (3 = March, 4 = April, 5 = May, ..., 14 = February)
K the year of the century (year mod 100).
J is the century (For example, in 1995 the century would be 19, even though it was the 20th century.)

The formulas rely on the mathematician's definition of modulo division, which means that −2 mod 7 is equal to positive 5. Unfortunately, the way most computer languages implement the remainder function, −2 mod 7 returns a result of -2. So, to implement Zeller's congruence on a computer, the formulas should be altered slightly to ensure a positive numerator. The simplest way to do this is to replace − 2J by + 5J:

Zeller used decimal arithmetic, and found it convenient to use J and K in representing the year. But when using a computer, it is simpler to handle the modified year Y, which is Y - 1 during January and February:

And you can write it in Revit as: (zellerHdayFirst Integer Parameter)

FirstDay + rounddown(((if(monthDRV = 1, 13, if(monthDRV = 2, 14, monthDRV))) + 1) * 2.6) + yearDRV + rounddown(yearDRV / 4) + (6 * rounddown(yearDRV / 100)) + rounddown(yearDRV / 400) - (7 * rounddown((FirstDay + rounddown(((if(monthDRV = 1, 13, if(monthDRV = 2, 14, monthDRV))) + 1) * 2.6) + yearDRV + rounddown(yearDRV / 4) + (6 * rounddown(yearDRV / 100)) + rounddown(yearDRV / 400)) / 7))

THE MATRIX

In the matrix I have two kinds of Labels (some overlapping):

DAYS and GRAYS

DAYS are all we need. GRAYS are just the decorative ones showing values from the last / next month in.. well.. GRAY.

Zeller's Algorithm starts the week from Saturday with a value of zero  (0 = Saturday, 1 = Sunday, 2 = Monday, ...) I've done the same naming the Matrix Elements. So let's say d32 means:

d = it's a DAYS Label
3 =  Third Row
2 = Zeller Day Two which is Monday

Notice the first group of GRAYS start from g12 to g10. This Month has to start somewhere in the first week! and the last day that can be is Sunday (d11) so the GRAYS should end there.
As this group of GRAYS show the last days of the last month I needed to figure out the count of the days in the last month.

cntDAYSlast (Integer Parameter)

if(or(monthDRVlast = 1, monthDRVlast = 3, monthDRVlast = 5, monthDRVlast = 7, monthDRVlast = 8, monthDRVlast = 10, monthDRVlast = 12), 31, if(or(monthDRVlast = 4, monthDRVlast = 6, monthDRVlast = 9, monthDRVlast = 11), 30, if(Leap, 29, 28)))

Saying if the Last Month is
1,3,5,7,8,10,12 -> 31 days
else if
4,6,9,11 -> 30 days
else if
Leap Year -> 29
not anything above :) 28

Labels from g12 to g10 are counting down from that number before the first day of this month and g12v to g10v are controlling the visibility of these labels.

DAYS

We know the Zeller first day. (zellerHdayFirst)
And the count of the days in this month. (cntDAYS).
So we count the days of this month :)

Second part of the GRAYS (g52 to g61) just count from the end of this month. These Labels start from g52 because the earliest this month can start from is Monday (d12) and shortest this month can only be is 28 days.

SELECTED DATE

For the selected date instead of creating a matrix and playing with visibility I've placed a family where I can move with Revit dimensions. It would definitely give more options to have a Matrix but would have taken a longer time. (By the time I've got to this point I've started to realize I've got things to do. Just like now realizing it's 3 AM and  I've got to go to bed :)

zellerHdaySelected (Integer Parameter) Find the Zeller Day of the Selected Day
todayKX (Integer Parameter) Multiplier for the Column
todayKY (Integer Parameter) Multiplier for the Row
todayX (Length Parameter) Driving the Today Ring in X direction
todayY (Length Parameter) Driving the Today Ring in Y direction