sourcehypertextpubliclatbsconverterkalends.js

const getFormData = planet =>
	Object.fromEntries(
		[...$(`#${planet.meta.calendar}`).$$("select, input")].map(el => [
			el.id.replace(planet.meta.prefix, ""),
			+el.value
		])
	);

const setFormData = (planet, data) => {
	for (timespan in data) {
		const el = $(`#${planet.meta.prefix}${timespan}`);
		if (el) {
			el.value = el.matches(".converter-time *")
				? `${data[timespan]}`.padStart(2, "0")
				: `${data[timespan]}`;
		}
	}
};

const updateForms = planet => {
	const planetDate = planet.toUnix(getFormData(planet));

	setFormData(Earth, Earth.fromUnix(planetDate));
	setFormData(Mars, Mars.fromUnix(planetDate));
};

let Earth = {
	meta: {
		calendar: "gregorian",
		prefix: "t-"
	},
	fromUnix: uDate => {
		const jsDate = new Date(uDate);

		return {
			era: +(jsDate.getUTCFullYear() > 0),
			year:
				jsDate.getUTCFullYear() > 0
					? jsDate.getUTCFullYear()
					: abs(jsDate.getUTCFullYear()) + 1,
			month: jsDate.getUTCMonth() + 1,
			day: jsDate.getUTCDate(),
			hour: jsDate.getUTCHours(),
			minute: jsDate.getUTCMinutes(),
			second: jsDate.getUTCSeconds(),
			weekday: jsDate.getUTCDay()
		};
	},
	toUnix: tDate => {
		const astronomicalYear = tDate.era ? tDate.year : 1 - tDate.year;
		const adjustedYear =
			astronomicalYear >= 0 && astronomicalYear <= 99
				? 100
				: astronomicalYear;

		let uDate = Date.UTC(
			adjustedYear,
			tDate.month - 1,
			tDate.day,
			tDate.hour,
			tDate.minute,
			tDate.second,
			500
		);

		if (adjustedYear != astronomicalYear) {
			const jsDate = new Date(uDate);
			jsDate.setUTCFullYear(astronomicalYear);
			uDate = +jsDate;
		}

		return uDate;
	}
};

let Mars = {
	meta: {
		calendar: "achillean",
		prefix: "m-"
	},
	epoch: 1611844500000, // 00.00.00 1 Areia 1 AA = 2021-01-28T14:35:00Z
	day: 24 * 60 * 60 * 1000 + 39 * 60 * 1000 + 35244,
	monthLengths: [
		[
			28, 28, 28, 28, 28, 27, 28, 28, 28, 28, 28, 27, 28, 28, 28, 28, 28,
			27, 28, 28, 28, 28, 28, 27
		], // Hollow sweep
		[
			28, 28, 28, 28, 28, 27, 28, 28, 28, 28, 28, 27, 28, 28, 28, 28, 28,
			27, 28, 28, 28, 28, 28, 28
		] // Leap sweep
	],
	sweepLengths: [669, 668, 669, 668, 669, 668, 669, 668, 669, 669], // 1 to 10, rather than 0 to 9
	decadeInDays: 6686
};
Mars.sec = Mars.day / (24 * 60 * 60);

Mars.fromUnix = uDate => {
	let mDate = {
		sweep: 1,
		month: 1,
		day: 1,
		gander: 0,
		min: 0,
		sec: 0,
		weekday: 0
	};

	const rataDie = floor((uDate - Mars.epoch) / Mars.day);
	const secOfDay = floor(
		(uDate - Mars.epoch - rataDie * Mars.day) / Mars.sec
	);

	mDate.gander = floor(secOfDay / 3600);
	mDate.min = floor((secOfDay % 3600) / 60);
	mDate.sec = secOfDay % 60;

	const currentDecade = floor(rataDie / Mars.decadeInDays);
	const dayOfDecade = rataDie - currentDecade * Mars.decadeInDays;

	/* Construct an array listing how many days have passed in the decade at the start of each sweep */
	let daysBeforeSweep = [0];
	Mars.sweepLengths.forEach((sweep, idx) => {
		daysBeforeSweep.push(!idx ? sweep : daysBeforeSweep[idx] + sweep);
	});

	/* Note to self — convert this to .findLastIndex() once it becomes more widely supported! */
	const sweepOfDecade =
		daysBeforeSweep.filter(ds => dayOfDecade - ds >= 0).length - 1;
	const dayOfSweep = dayOfDecade - daysBeforeSweep[sweepOfDecade];
	const isLeapSweep = Mars.sweepLengths[sweepOfDecade] == 669;

	mDate.sweep = currentDecade * 10 + sweepOfDecade + 1;

	/* Same as `daysBeforeSweep`, this time with months of the sweep */
	let daysBeforeMonth = [0];
	Mars.monthLengths[+isLeapSweep].forEach((month, idx) => {
		daysBeforeMonth.push(!idx ? month : daysBeforeMonth[idx] + month);
	});

	const monthOfSweep =
		daysBeforeMonth.filter(dm => dayOfSweep - dm >= 0).length - 1;
	const dayOfMonth = dayOfSweep - daysBeforeMonth[monthOfSweep];

	mDate.month = monthOfSweep + 1;
	mDate.day = dayOfMonth + 1;
	mDate.weekday = modulo(dayOfMonth, 7);

	return mDate;
};

Mars.toUnix = mDate => {
	let rataDie = 0;

	/* Zero-indexed Mars date, for ease of calculations */
	const zeroDate = {
		decades: floor((mDate.sweep - 1) / 10),
		sweeps: modulo(mDate.sweep - 1, 10),
		months: mDate.month - 1,
		days: mDate.day - 1,
		ganders: mDate.gander,
		mins: mDate.min,
		secs: mDate.sec
	};

	rataDie += zeroDate.decades * Mars.decadeInDays;
	for (let sweep = 0; sweep < zeroDate.sweeps; sweep++) {
		rataDie += Mars.sweepLengths[sweep];
	}
	for (let month = 0; month < zeroDate.months; month++) {
		rataDie += Mars.monthLengths[0][month];
	}
	rataDie += zeroDate.days;

	const currentTime =
		zeroDate.ganders * 60 * 60 + zeroDate.mins * 60 + zeroDate.secs + 0.5;

	const uDate = Mars.epoch + rataDie * Mars.day + currentTime * Mars.sec;

	return uDate;
};

let Venus = {
	meta: {
		calendar: "phosphori",
		prefix: "v-"
	},
	epoch: -62104657150467, // 00.00.00 1 Prudence 1 AP = 0001-12-25T02:20:49.553Z (solar midnight in Bethlehem)
	day: 86_400_000,
	monthLengths: [
		[
			28, 28, 28, 28, 28, 28, 28, 28
		], // Hollow sweep
		[
			28, 28, 28, 28, 28, 28, 28, 29
		] // Leap sweep
	],
	sweepLengths: [225, 225, 224, 225, 225, 224, 225, 225, 224, 225], // 1 to 10, rather than 0 to 9
	decadeInDays: 2247
};

let Stardate = {
	meta: {
		calendar: "stardate",
		prefix: "sd-"
	},
	epoch: -14159040000, // 0.0 = 1969-07-21T02:56:00Z
	day: 87500 * 1000
};
Stardate.fromUnix = uDate => {
	return round(10 * ((uDate - Stardate.epoch) / Stardate.day)) / 10;
};
Stardate.toUnix = sdDate => {
	return sdDate.stardate * Stardate.day + Stardate.epoch;
};

documentReady(() => {
	for (const el of $$("#gregorian input, #gregorian select")) {
		el.on("change", () => updateForms(Earth));
	}
	for (const el of $$("#achillean input, #achillean select")) {
		el.on("change", () => updateForms(Mars));
	}
});