My Porch Lights Know What Holiday It Is
On the evening of July 4th, my porch and the permanent outdoor strips came up red, white, and blue at sunset and shut off at 5am. I touched nothing. The house computed that it was Independence Day, picked the theme, and ran the show, first time through the new code.
the problem with holiday lights
Automating one holiday is trivial: hardcode the date, pick a color, done. The reason most people stop there is the calendar itself. Christmas sits still, but Thanksgiving floats to the fourth Thursday of November, Labor Day to the first Monday of September, Memorial Day to the last Monday of May. And Easter is computed from lunar tables via an algorithm with a papal history and the word “golden number” in it.
You can chase this with a calendar integration or an API. I wanted the house to know on its own, offline, forever. So: date math.
the holiday checker
The core is one Node-RED function node, a couple hundred lines of JavaScript, that answers “what holiday is it, if any?” from new Date() alone. Three kinds of rules cover the entire US calendar:
Fixed dates. July 4th, Halloween, Veterans Day, Valentine’s. One comparison each.
Nth-weekday rules. A small helper computes “the Nth
Easter. The anonymous Gregorian computus, the classic sequence of integer divisions that produces the month and day from the year. Mine is transcribed from the reference algorithm, tested against a table of the next twenty Easters. I understand maybe 60% of why it works and 100% of that it works, which for porch lighting is the right ratio.
The function runs daily and publishes the answer to a holiday select entity in Home Assistant, so anything else in the house can react to it too, and the dashboard shows what the house thinks today is.
from answer to light show
Each holiday maps to a scene plus a theme for the LIFX strips: palettes and motion effects for the permanent outdoor strips, matching colors on the porch bulbs. Thanksgiving is ambers and oranges, St. Patrick’s rolls greens, New Year’s gets a slow gold shimmer. Two default scenes, a standard warm white and a late-night dim, cover the ~320 non-holiday evenings.
Sunset triggers the day’s scene; a 5am rule shuts it down. The mapping table is the only part I still edit, and only when we decide a holiday deserves better colors. My kids have opinions about this table. Strong ones.
why compute instead of subscribe
A holiday calendar integration would give me the same answers with less code. I went with math for three reasons that generalize:
It has no dependencies. No API keys expiring, no calendar source going stale in year three. Date math from 1876 still runs.
It fails loud instead of quiet. Broken calculation shows the wrong thing on a dashboard entity today. Dead calendar feed shows nothing until Thanksgiving arrives unlit.
And the code is the documentation. Anyone (including me, later) can read the function and see exactly why the lights think it’s Labor Day.
the code
Trimmed version of the function node’s interesting parts, entity names generic:
function nthWeekday(year, month, weekday, n) {
// month 0-11, weekday 0=Sun; n<0 counts from month end
if (n > 0) {
const first = new Date(year, month, 1);
const offset = (7 + weekday - first.getDay()) % 7;
return new Date(year, month, 1 + offset + (n - 1) * 7);
}
const last = new Date(year, month + 1, 0);
const offset = (7 + last.getDay() - weekday) % 7;
return new Date(year, month, last.getDate() - offset);
}
function easter(year) { // Gregorian computus
const a = year % 19, b = Math.floor(year / 100), c = year % 100;
const d = Math.floor(b / 4), e = b % 4;
const f = Math.floor((b + 8) / 25), g = Math.floor((b - f + 1) / 3);
const h = (19 * a + b - d - g + 15) % 30;
const i = Math.floor(c / 4), k = c % 4;
const l = (32 + 2 * e + 2 * i - h - k) % 7;
const m = Math.floor((a + 11 * h + 22 * l) / 451);
const month = Math.floor((h + l - 7 * m + 114) / 31); // 3=Mar, 4=Apr
const day = ((h + l - 7 * m + 114) % 31) + 1;
return new Date(year, month - 1, day);
}
const now = new Date(), y = now.getFullYear();
const holidays = [
{ name: "New Year's", date: new Date(y, 0, 1) },
{ name: "MLK Day", date: nthWeekday(y, 0, 1, 3) },
{ name: "Valentines", date: new Date(y, 1, 14) },
{ name: "St. Patricks", date: new Date(y, 2, 17) },
{ name: "Easter", date: easter(y) },
{ name: "Memorial Day", date: nthWeekday(y, 4, 1, -1) },
{ name: "Independence", date: new Date(y, 6, 4) },
{ name: "Labor Day", date: nthWeekday(y, 8, 1, 1) },
{ name: "Halloween", date: new Date(y, 9, 31) },
{ name: "Veterans Day", date: new Date(y, 10, 11) },
{ name: "Thanksgiving", date: nthWeekday(y, 10, 4, 4) },
{ name: "Christmas", date: new Date(y, 11, 25) },
];
const today = holidays.find(h =>
h.date.getMonth() === now.getMonth() &&
h.date.getDate() === now.getDate());
msg.payload = today ? today.name : "standard";
return msg;
Downstream, a switch node routes the name to its scene. Adding a holiday is one line in the table and one scene.
the evergreen part
This automation re-earns its keep on schedule. On a random Monday in September the porch goes Labor Day colors and someone asks about it, and the answer is “the house did the math.” I plan to add a photo per holiday to this post as the year turns over, which makes it the rare blog post with a built-in content calendar.
The lights have opinions about the calendar now. So far the calendar and I agree with them.



Post Comment