Annuals — more than just a birthday/holiday tracker

Hi all,

Annuals is an integration for tracking yearly-recurring events — birthdays, holidays, anniversaries, name days, memorials, pet birthdays, work anniversaries, or anything custom (even one-time events).

github.com/somansch/annuals

Why you’d want this

  • Never miss a birthday or anniversary again — get notified the morning of, or a week ahead of a milestone, using whatever notification setup you already have (mobile app, TTS, Alexa, …) or the bundled reminder blueprint, which sets that up for you with no YAML.
  • Know at a glance which occurrence it is — “Anna turns 30” instead of just “Anna’s birthday”, computed automatically from the year you entered once.
  • Highlight who and what matters most — flag close family as VIP so they always stand out, and let round-number milestones (18th, 30th, 50th, …) mark themselves as Important automatically — no manual bookkeeping every year.
  • Import a whole country’s public holidays in a few clicks, categorized (public, bank, school breaks, religious, …), with a choice between each holiday’s actual date, its practically-observed date (many countries shift a weekend holiday to a nearby weekday), or both.
  • Bring in a whole contact list at once via CSV, ICS calendar, or vCard import, instead of adding entries one by one.
  • See it all on your dashboard, styled to match your setup, without touching YAML (incl. your existing calendars).

Everything is config-flow/UI driven — no YAML required anywhere, including a very flexible dashboard card and the bundled reminder blueprint.

What it does

For every event you add, Annuals reports how many days until its next occurrence, and — if you gave it a year — which occurrence number that will be (e.g. someone’s 30th birthday). Each event becomes its own sensor, and every event type also gets its own aggregate calendar entity, so you can drop it straight onto the built-in Calendar dashboard.

Features

  • :birthday_cake: 8 event types out of the box: birthday, anniversary, name day, wedding anniversary, memorial, pet birthday, work anniversary, custom — each with its own icon
  • :star: VIP annual — flag one event to always stand out, independent of type or occurrence number
  • :bullseye: Important annual — automatic milestone detection (18th/30th/50th birthday, 25th anniversary, …), fully customizable per event type
  • :date: Per-type calendars (calendar.annuals_<type>) for a native calendar view, plus a sensor per event with days-remaining, occurrence number, VIP/Important flags
  • :inbox_tray: CSV import — add a whole contact list at once, VIP flag included
  • :inbox_tray: ICS and vCard import — add a whole contact/event list at once
  • :crossed_flags: Holidays import - covers 250+ countries and territories and 150+ languages, with the choice to import each holiday’s actual date, its practically-observed date (e.g. a Saturday US federal holiday, observed the preceding Friday), or both as separate events
  • :bell: Reminder blueprint — a ready-made automation blueprint for lead-time notifications (mobile push, HA notifications, TTS, to-do list, or your own custom actions), no YAML required
  • :broom: One-click “delete all Annuals data” if you want to start over
  • :globe_showing_europe_africa: Translated UI in 15 languages (English, German, French, Dutch, Polish, Spanish, Italian, Portuguese (BR), Russian, Swedish, Chinese, Czech, Norwegian, Danish, Turkish)

Custom dashboard card

custom:annuals-card , bundled with the integration — no separate frontend install, just search “Annuals Card” in the card picker. Everything’s set through a full visual editor: event filters, VIP/Important-only views, colors, fonts, highlight tinting, badges, background…

Every color/font it exposes is also a --annuals-* CSS variable with a theme fallback, so you can theme every card at once — no card-mod required , though it still works if you use it.

That’s the default out-of-the-box layout:

…and you can go wild with detailed customization without card-mod or yaml edit:

Including animated icons and a Compact mode:
birthdaysmallanimated

holidaysmallanimated

…and a handy timeline visualization

Installation

Annuals is part of the default HACS integration list:

  1. Open HACS in Home Assistant
  2. Search for “Annuals”
  3. Click the “Download” button
  4. Restart HA

The dashboard card registers itself automatically, no extra step. Manual installation works the same way — see the README.

Feedback, issues, and feature requests are very welcome on GitHub.


If you find it useful and want to support development, there’s a Buy Me a Coffee link on the repo — totally optional.

“… Settings → Devices & Services → Add Integration → “Annuals” ,…”.
There is no Annuals integration.

Have you done a HA restart after download from HACS?

Yes and I just did it again… No “annual”.

Have you added the custom repository?

If so, have you done the download in HACS before your reboot?

Hi, how does the integration handle February 29 anniversaries?

The integration accounts for leap years (February 29) when calculating the number of days until the next anniversary.

If your birthday is on February 29th it is calculated correctly, and it does not fall back to March 1st — instead, in non-leap years it falls back to February 28th

Concretely:

  • Leap year (e.g. 2028): the event falls exactly on February 29th.
  • Non-leap year (2025, 2026, 2027, 2029, …): the event falls on February 28th.

This is a deliberate design choice: it guarantees an occurrence every year (not just once every four years), and occurrence_number (e.g. “turning 30”) still counts correctly, since it’s simply computed as target year - birth year, independent of the exact day.

I got it now

1 Like

Hmm, I prefer march first, which is the day after february 28. But I agree it is somewhat arbitrary.

Just updated my answer above :wink:

BTW: Great question - I added the description to the readme.

The reason I prefer March 1st btw is because in any year you have not had your birthday yet on Februari 28, but you always have on March 1. When you fall back a day, this is no longer true.

I am not sure (yet) to understand whatit adds. I have multiple calendars in Google that act as triggers (whole day events) in automations. What would Annuals add to this ?

I also maintained all birthdays in a calendar as a whole day event in an annual series. But, I was missing the information about the anniversary. This was the main driver for this integration.

You can set your birthday to 1st March, but then you need to remember every forth year to celebrate a day before :laughing:

I would be interested what the best practise is for this scenario…

1 Like

I had the anniversaries integration until it fell unmaintained. I created a template blueprint to get around it. Since then the integration found a new maintainer I think, but I kept using my blueprint. But it isn’t perfect due to limitations the template blueprints had at the time. It needs the entity id to be just right to prevent double calendar events. I’ll probably switch to yours when I can find the time.

1 Like

Just released v2.0.0 with powerful new features:

  • Definition of VIP annuals
  • Calculation of important annual events
  • custom lovelace card to go crazy visually without card-mod or yaml edit

Feedback and stars on Github always welcome :slightly_smiling_face:

Just started to try it using the csv mode, since I track about 500 items.
Every time i upload the csv it duplicates the files. Is there any way to prevent that.

@carltonwb , the CSV upload is meant for a one time import. You can easily delete everything under “Annual Settings” and reimport a changed CSV.

I will try to create a solution for scheduled CSV imports without duplication.

New version 2.0.1 is now online:

Added

  • annuals.import_csv action - import events from CSV via automation/script instead of only through the UI, useful for keeping events in sync with a centrally maintained CSV file on a schedule. Accepts either inline content or a file_path on the HA host.

Fixed

  • Re-importing a CSV (via the UI or the new action) no longer creates duplicate events. Rows are now matched against existing entries by type + day/month + name; a match updates that event in place instead of adding a second one.