Upcoming Birthdays (robust & flexible)
Here’s a rather robust and flexible list of upcoming birthdays that
- shows link, next birthday, and age someone will turn to
- has a configurabe date range (from today, for a “natural language” duration)
- sorts by next birthday
- uses locale settings from the language Obsidian is set to
- doesn’t choke when crossing the year boundary
- has YAML searchterm configuration
- has YAML “natural language” duration configuration; “odd” data like
duration: 0.5 years
will be auto-translated to6 months
(or6 Monate
in German) - has YAML date format configuration (yay!)
- has a configurable info line above the table (can be disabled)
Upcoming birthdays with English settings, custom date format and info line
The technically-minded may also find interesting:
- utility functions that can be used for table data
- custom function for “where”
- comparator function for “sort”
- easily translatable “info line” with parameters
- how I need to undo Dataview’s YAML pre-parsing of dates and durations
Upcoming birthdays with German settings, custom date format and info line
Requirements
- Dataview 0.3.3+
-
moment.js
(comes with Obsidian) - Notes to be included must have a birthday as
YYYY-MM-DD
in the frontmatter:birthday: 1959-07-19
- The note using this script must have the parameters
searchterm
and a (more or less) “natural language”duration
in the frontmatter:#searchterm: "#family or #friends" searchterm: '"People"' duration: 1 year
- The note using this script can have an additional
dateformat
in the frontmatter:
If left out or empty, it will default todateformat: "ddd, D MMMM YYYY"
YYYY-MM-DD
.
Code
YAML frontmatter example
#searchterm: "#family or #friends"
searchterm: '"People"'
duration: 1 year
dateformat: "ddd, D MMMM YYYY"
dataviewjs
code
This contains some utility functions that can be used in your table, like nextBirthday()
, turns()
.
var start = moment().startOf('day');
var end = moment(start).add(dv.current().duration);
var dateformat = "YYYY-MM-DD";
if (dv.current().dateformat) { dateformat = dv.current().dateformat; }
// info text above table, {0}=duration, {1}=start date, {2}=end date
// parameters can be left out, or the string empty
var infotext = "Upcoming birthdays for {0} from now ({1} – {2})<br><br>";
//======================================================================
function nextBirthday(birthday) {
// Get person’s next birthday on or after "start"
// returns a moment
// need to "unparse" because DV has already converted YAML birthday to DateTime object
// shouldn’t harm if already a string
var bday = moment(birthday.toString());
var bdayNext = moment(bday).year(start.year());
if (bdayNext.isBefore(start, 'day')) {
bdayNext.add(1, "year");
}
return bdayNext;
}
function turns(birthday) {
// Get the age in years a person will turn to on their next birthday
// need to "unparse" because DV has already converted YAML birthday to DateTime object
// shouldn’t harm if already a string
var bday = moment(birthday.toString());
return nextBirthday(birthday).diff(bday, 'years');
}
function showBirthday(birthday) {
// Determine if this birthday is in the range to be shown
// including the start date, excluding the end date
// because that comes from a duration calculation
// for use with "where", returns true or false
if (birthday) {
// need to "unparse" because DV has already converted YAML birthday to DateTime object
// shouldn’t harm if already a string
var bday = moment(birthday.toString());
var bdayNext = nextBirthday(birthday);
if (bdayNext.isBetween(start, end, 'day', '[)')) {
return true;
} else {
return false;
}
} else {
return false;
}
}
function sortByNextBirthday(a, b) {
// comparator function for "sort"
if (nextBirthday(a).isBefore(nextBirthday(b))) {
return -1;
}
if (nextBirthday(a).isAfter(nextBirthday(b))) {
return 1;
}
// they’re equal
return 0;
}
//======================================================================
dv.paragraph(infotext.format(moment.duration(dv.current().duration.toString()).humanize(), start.format(dateformat), end.format(dateformat)));
dv.table(
["Name", "Birthday", "Turns"],
dv.pages(dv.current().searchterm)
// use a function to see if this birthday is in range to be shown
.where(p => showBirthday(p.birthday))
// use a comparator function to sort by next birthday
.sort(p => p.birthday, 'asc', sortByNextBirthday)
.map(p => [
p.file.link,
p.birthday ? nextBirthday(p.birthday).format(dateformat) : '–',
turns(p.birthday)
])
);
Notes
-
Dataview actually parses the cell content, so if you for example set the date format to
D. MMMM
, you’ll get large vertical spacing. This is because DV thinks something like17. May
must be an ordered list item and parses it accordingly. Which in turn will ruin your vertical spacing.
See 0.3.3: table item converts to ordered list, ruining vertical spacing · Issue #199 · blacksmithgu/obsidian-dataview · GitHub. -
Using durations > 1 year (like
2 years
) work, but don’t make much sense. The table will only display a person’s next birthday. So no duplicates.