// Clock label formats and the memento mori bar. Calendar date math lives in // qs.Ui's CalendarModel.js, shared with the Quick Settings calendar. // ---- Bar label formats. Right-clicking the clock walks these in order and // writes the result back to shell.json, so the label the bar shows and // the format the config stores are always the same thing. // // The locale-shaped time presets are each followed by their 12-hour twin, so // the walk from a 24-hour label to the same label in AM/PM is a single right // click rather than a lap of the ring. The ISO preset is deliberately left // without one: ISO 8601 writes time on a 24-hour clock, so an AM/PM variant // would contradict the only thing that format is for. var CLOCK_FORMATS = [ "dddd HH:mm", "dddd h:mm AP", "HH:mm", "h:mm AP", "ddd d MMM HH:mm", "ddd d MMM h:mm AP", "d MMMM 'W'ww yyyy", "yyyy-MM-dd HH:mm" ] // Vertical bars have room for a few stacked lines and nothing else, so the // ring stays short. AM/PM costs a fourth line, which is why only the plain // time carries it here. var VERTICAL_CLOCK_FORMATS = [ "HH\n—\nmm", "h\n—\nmm\nAP", "dd\nMMM\n'W'ww\n''yy", "HH\nmm" ] function clockFormats(vertical) { return vertical ? VERTICAL_CLOCK_FORMATS.slice() : CLOCK_FORMATS.slice() } // The presets in a fixed order, plus the configured alternate and current // format when they are something else. The order must not depend on which // entry is current: cycling writes the result back to shell.json, and a ring // that reshuffled itself around the current value would bounce between two // entries instead of walking. function clockFormatRing(configured, configuredAlt, presets) { var ring = [] var candidates = (presets || []).concat([configuredAlt, configured]) for (var i = 0; i < candidates.length; i++) { var format = String(candidates[i] === undefined || candidates[i] === null ? "" : candidates[i]) if (format === "" || ring.indexOf(format) !== -1) continue ring.push(format) } return ring.length > 0 ? ring : ["HH:mm"] } // Next entry after `current`. An unknown current format (a hand-written one // that is not in the ring) starts the walk at the top. function nextClockFormat(ring, current) { if (!ring || ring.length === 0) return "" var index = ring.indexOf(String(current === undefined || current === null ? "" : current)) return ring[(index + 1) % ring.length] } // Memento mori. The default span is a round number rather than anything from // an actuarial table: the point of the bar is the reminder, not the // arithmetic, and whoever wants a different number can say so. var DEFAULT_LIFE_EXPECTANCY = 90 // A birth year rather than an age, so the bar keeps counting on its own // instead of going stale the moment it is entered. 0 means "not set", which // is also what a blank, malformed, future, or implausibly distant year means. function parseBirthYear(value, currentYear) { var now = Math.round(Number(currentYear)) if (!isFinite(now)) return 0 var text = String(value === undefined || value === null ? "" : value).replace(/^\s+|\s+$/g, "") if (!/^\d{4}$/.test(text)) return 0 var year = parseInt(text, 10) if (!isFinite(year) || year > now || year < now - 120) return 0 return year } // Whole years, the way people say their age: born in 1979 makes you 47 for // all of 2026, whichever side of your birthday today falls. function ageFromBirthYear(birthYear, currentYear) { var born = parseBirthYear(birthYear, currentYear) if (born <= 0) return 0 return Math.round(Number(currentYear)) - born } // 0 means "not set", which is also what a blank, negative, fractional, or // absurd entry means — the life bar simply stays hidden. function parseAge(value) { var text = String(value === undefined || value === null ? "" : value).replace(/^\s+|\s+$/g, "") if (!/^\d+$/.test(text)) return 0 var years = parseInt(text, 10) if (!isFinite(years) || years <= 0 || years > 120) return 0 return years } // Unset or nonsense falls back to the default rather than to zero, so the // bar always has something to measure against. function parseLifeExpectancy(value) { var text = String(value === undefined || value === null ? "" : value).replace(/^\s+|\s+$/g, "") if (!/^\d+$/.test(text)) return DEFAULT_LIFE_EXPECTANCY var years = parseInt(text, 10) if (!isFinite(years) || years <= 0 || years > 150) return DEFAULT_LIFE_EXPECTANCY return years } function lifeProgress(age, expectancy) { var years = parseAge(age) var span = parseLifeExpectancy(expectancy) if (years <= 0 || span <= 0) return 0 return Math.max(0, Math.min(1, years / span)) } function lifeProgressPercent(age, expectancy) { return Math.round(lifeProgress(age, expectancy) * 100) } if (typeof module !== "undefined") { module.exports = { parseAge: parseAge, parseBirthYear: parseBirthYear, ageFromBirthYear: ageFromBirthYear, parseLifeExpectancy: parseLifeExpectancy, lifeProgress: lifeProgress, lifeProgressPercent: lifeProgressPercent, clockFormats: clockFormats, clockFormatRing: clockFormatRing, nextClockFormat: nextClockFormat } }