Skip to content

Instantly share code, notes, and snippets.

@ddlsmurf
Last active December 25, 2021 22:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ddlsmurf/d98b7e18d12c21592700136545ed542c to your computer and use it in GitHub Desktop.
Save ddlsmurf/d98b7e18d12c21592700136545ed542c to your computer and use it in GitHub Desktop.
Calculates more interesting dates to celebrate if your existence duration is the main variable
<html>
<head>
<title>Birthdates ! </title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js" integrity="sha512-qTXRIMyZIFb8iQcfjXWCO8+M5Tbc38Qi5WzdPOYZHIlZpzBHG3L3by84BBBOiRGiEb7KKtAOAs5qYdUiZiQNNQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.34/moment-timezone-with-data.js" integrity="sha512-oqSECbLRRAy3Sq2tJ0RmzbqXHprFS+n7WapvpI1t0V7CtV4vghscIQ8MYoQo6tp4MrJmih4SlOaYuCkPRi3j6A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- Select 2 -->
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
</head>
<body>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
padding: 2px;
}
.box {
border-width: 1px;
border-style: solid;
border-color: black;
margin: 2px;
padding: 2px;
}
#age {
color: darkgrey; /* need color not colour, but grey and gray is ok - fucking yank standard */
}
#ageAdvice {
font-size: small;
font-style: italic;
color: lightpink;
}
.hidden {
display: none;
}
table, td, th {
border: 1px solid black;
border-spacing: 1em;
border-collapse: collapse;
margin: 2px;
padding: 2px;
}
.linear {
color: darkgrey; /* again with the gr[ae]y https://stackoverflow.com/questions/8318911/why-does-html-think-chucknorris-is-a-color */
}
</style>
<div class="box">
<span>Birthday and time:</span>
<input id="birthDT" name="birthDT" type="datetime-local"></input>
<span>in:</span>
<select name="birthTimeZone" id="birthTimeZone"></select>
<br>
<span id="age" class="hidden">
<span id="ageDescription"></span>
<span id="ageAdvice">You have a lot of minutes left to live, and the worst day in your life isn't so bad, it's only the worst so far. You have good reason to be happy now !</span>
</span>
<br>
<span>Show times for:</span>
<select name="displayTimeZone" id="displayTimeZone"></select>
</div>
<table>
<thead>
<tr>
<th>Date</th>
<th>Time left</th>
<th>Celebration</th>
</tr>
</thead>
<tbody id="birtdates">
</tbody>
</table>
<script lang="javascript">
const j = (x) => JSON.stringify(x)
function gid(id) { return document.getElementById(id); }
function el(parent, tagname, attributes, ...content) {
const newEl = document.createElement(tagname);
content.forEach(item =>
newEl.appendChild(typeof item == "string" ? document.createTextNode(item) : item)
);
if (typeof(content) == "string")
newEl.innerText = content;
if (parent != null)
parent.appendChild(newEl);
if (attributes) {
Object.keys(attributes).forEach((x) => {
newEl.setAttribute(x, attributes[x]);
});
}
return newEl;
}
function emptyEl(el) { el.innerHTML = ""; }
// of course fn stands for format number
const fn = (n, d) => n.toLocaleString(undefined, { maximumFractionDigits: d });
let presetBirthTz = window.localStorage.getItem("presetBirthTz");
let presetBirth = window.localStorage.getItem("presetBirth");
const docElements = {
displayTimeZone: gid("displayTimeZone"),
birthTimeZone: gid("birthTimeZone"),
birthDT: gid("birthDT"),
birtdates: gid("birtdates"),
age: gid("age"),
ageDescription: gid("ageDescription"),
};
const guessTZ = moment.tz.guess();
const timezones = (function separateTimezonesBySlashIntoGroups(all) {
const groups = {};
all.sort();
all.forEach(name => {
// A bit smarter version of .split("/")
const match = /^(?:([^\/]+)\/)?(.+?)$/.exec(name);
if (!match)
throw new Error("Error timezone " + j(name) + " doesn't match regexp");
const [_dontCare, groupName, entry] = match;
const displayName = entry.replace(/_/g, " ").replace(/\//g, " ‣ ");
const groupKey = groupName || "";
(groups[groupKey] || (groups[groupKey] = [])).push({name, displayName});
});
return groups;
})(moment.tz.names());
// Try guessing what this looks like before running this:
// console.log(timezones);
function timezoneListPopulate(select, onChange) {
emptyEl(select);
el(select, "option", { disabled: "disabled" }); // Without this, it just selects the first timezone in the list
const groupNames = Object.keys(timezones);
groupNames.sort();
groupNames.push(groupNames.shift()); // Move those without prefix to the end
groupNames.forEach((groupName) => {
const groupElement = groupName == "" ? select : el(select, "optgroup", { label: groupName });
timezones[groupName].forEach(({name, displayName}) => {
el(groupElement, "option", { value: name }, displayName);
})
})
const $select = $(select).select2({ });
$select.on("change", () => onChange($select.val()));
if (guessTZ)
$select.val(guessTZ).trigger('change');
}
$(document).ready(function() {
const enteredValues = {
dtBirth: null,
tzBirth: null,
tzDisplay: null,
};
function getBirth() {
const {dtBirth, tzBirth} = enteredValues;
return (dtBirth && tzBirth) ? moment.tz(dtBirth, moment.HTML5_FMT.DATETIME_LOCAL, tzBirth) : undefined;
}
function updateAge() {
const birth = getBirth();
docElements.age.classList[birth ? "remove" : "add"]("hidden");
if (!birth) return;
const now = moment();
const duration = moment.duration(now.diff(birth));
docElements.ageDescription.innerText = [
"You are",
fn(duration.asYears(), 2),
"years old, or",
fn(duration.asMonths(), 2),
"months, or",
fn(duration.asWeeks(), 2),
"weeks, or",
fn(duration.asDays(), 1),
"days, or",
fn(duration.asHours(), 1),
"h, or",
fn(duration.asMinutes(), 0),
"m, or",
fn(duration.asSeconds(), 0),
"s.",
].join(" ");
}
setInterval(updateAge, 1000);
function updateRelativeDates() {
const {tzDisplay} = enteredValues;
if (!tzDisplay) return;
document.querySelectorAll("[data-ts]").forEach((item) => {
const date = moment(parseInt(item.getAttribute('data-ts'), 10)).tz(tzDisplay);
item.querySelector(".absDate").innerText = date.format('lll');
item.querySelector(".relDate").innerText = date.fromNow();
});
}
setInterval(updateRelativeDates, 1000);
function createBirthday(descriptionHTML, moment, klass) {
const desc = el(null, "td", {class: "dateDescription"});
desc.innerHTML = descriptionHTML;
el(docElements.birtdates, "tr", {'data-ts': moment.valueOf(), class: klass},
desc,
el(null, "td", {class: "absDate"}, ""),
el(null, "td", {class: "relDate"}, ""),
desc,
);
}
const reasonableExpectancyInYears = 200;
const reasonableExpectancyInMS = reasonableExpectancyInYears * 365.25 * 24 * 60 * 60 * 1000;
function update() {
const birth = getBirth();
const {tzDisplay} = enteredValues;
if (!(birth && tzDisplay)) return;
const list = [];
const powers = (root, start, count) =>
(new Array(count)).fill(0).map((_, i) => Math.pow(root, i + start));
const linear = (start, count) =>
(new Array(count)).fill(0).map((_, i) => i + start);
const powersAfter = (birth, timeUnit, root, start, count) =>
powers(root, start, count).map((count, i) => [`${root}<sup>${i + start}</sup> (${fn(count, 0)}) ${timeUnit}`, birth.clone().add(count, timeUnit)]);
const linearAfter = (birth, timeUnit, start, count) =>
linear(start, count).map((num, i) => [`${num} ${timeUnit}`, birth.clone().add(num, timeUnit), "linear"]);
let dates = [];
dates = dates.concat(linearAfter(birth, "years", 1, reasonableExpectancyInYears));
dates = dates.concat(powersAfter(birth, "seconds", 2, 1, 30));
dates = dates.concat(powersAfter(birth, "minutes", 2, 1, 30));
dates = dates.concat(powersAfter(birth, "hours", 2, 1, 30));
dates = dates.concat(powersAfter(birth, "days", 2, 1, 30));
dates = dates.concat(powersAfter(birth, "weeks", 2, 1, 30));
dates = dates.concat(powersAfter(birth, "months", 2, 1, 30));
dates = dates.concat(powersAfter(birth, "years", 2, 1, 30));
dates = dates.concat(powersAfter(birth, "seconds", 10, 1, 10));
dates = dates.concat(powersAfter(birth, "minutes", 10, 1, 10));
dates = dates.concat(powersAfter(birth, "hours", 10, 1, 10));
dates = dates.concat(powersAfter(birth, "days", 10, 1, 10));
dates = dates.concat(powersAfter(birth, "weeks", 10, 1, 10));
dates = dates.concat(powersAfter(birth, "months", 10, 1, 10));
dates = dates.concat(powersAfter(birth, "years", 10, 1, 3));
const now = moment();
dates = dates.filter(([_, moment]) => {
const remainingMS = moment.diff(now);
return remainingMS > 0 && remainingMS < reasonableExpectancyInMS;
});
dates = dates.sort(([_da, momentA], [_db, momentB]) => momentA.diff(momentB));
emptyEl(docElements.birtdates);
dates.forEach(([desc, mom, klass]) =>
createBirthday(desc, moment.tz(mom, tzDisplay), klass)
)
updateRelativeDates();
};
const makeFieldUpdater = (field, storageKey) => (val) => {
enteredValues[field] = val;
if (storageKey)
window.localStorage.setItem(storageKey, val);
updateAge();
updateRelativeDates();
update();
}
timezoneListPopulate(docElements.birthTimeZone, makeFieldUpdater("tzBirth", "presetBirthTz"));
timezoneListPopulate(docElements.displayTimeZone, makeFieldUpdater("tzDisplay"));
docElements.birthDT.addEventListener("change", () => makeFieldUpdater("dtBirth", "presetBirth")(docElements.birthDT.value));
if (presetBirthTz) $(docElements.birthTimeZone).select2().val(presetBirthTz).trigger('change');
if (presetBirth) makeFieldUpdater("dtBirth")(docElements.birthDT.value = presetBirth);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment