Loading Now

My Porch Lights Know What Holiday It Is

Flat editorial illustration: a porch light post radiating holiday color themes in fanning arcs, with a small calendar-and-gears icon suggesting computed dates.

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 of “: fourth Thursday of November, first Monday of September, third Monday of January. Last-Monday holidays run the same helper backward from month’s end.

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.

My name is Skylar Pearce, I have been working as a System Administror since 2013 as well some side consulting work. During my career I have worked with everything from Active Directory and vCenter to configuring routers and switches and phone systems, documenting and scripting my way through the whole thing. I have a Security+ certification and am currently working on my PenTest+. Throughout my career I have gained almost all of my knowledge from blogs like this. It is now time for me to pay it back. Over time I have gathered scripts and tricks over the years that I will share on this site. A lot of the posts here will be mainly reference posts, some will be full on how to’s. I am happy to go into more depth on any other topics I go over here, just make a comment on a post. I will do my best to post once a day on weekdays but as I run out of ideas it may slow down. My WordPress skills are still growing so the site will likely get better over time as I learn. You can reach me at contact@allthesystems.com or on LinkedIn

Post Comment