Last active
December 4, 2015 19:00
-
-
Save hatzka-nezumi/295bb86c659151166d0c to your computer and use it in GitHub Desktop.
Levian calendar calculator. https://www.vikomprenas.com/levcal/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Levian Calendar calculator | |
// Copyright (C) 2015 Vi Komprenas <[email protected]> | |
// This is free and unencumbered software released into the public domain. <http://unlicense.org/> | |
// Requires jQuery (tested on 1.11.3). | |
// <div id="levcal">Loading!</div> | |
jQuery(function levcal(){ | |
var $ = window.jQuery; | |
var widgetcontent = ""; | |
widgetcontent += '<form>'; | |
widgetcontent += '<input type="button" value="Clear input" id="levcal-reset"></input>'; | |
widgetcontent += 'Gregorian calendar date (YYYY-MM-DD): <input type="text" autocomplete="off" id="levcal-gregorian"></input><br/>'; | |
widgetcontent += '<input type="button" value="Help" id="levcal-help"></input>'; | |
widgetcontent += '<a href="https://www.nationstates.net/nation=leveat/detail=factbook/id=484140">Levian calendar</a> date (Y/M/W.D or Y//X.D): <input type="text" autocomplete="off" id="levcal-levian"></input>'; | |
widgetcontent += '<div id="levcal-message"></div>'; | |
widgetcontent += '</form>'; | |
function message(mesg) { | |
$("#levcal-message").html(mesg + '<br/>'); | |
} | |
function defaultmessage() { | |
message("Levian calendar calculator. <a href=\"http://www.vikomprenas.com/levcal/\">Made by Vi Komprenas</a>.") | |
} | |
function daysInMonth(month, isLeapYear) { | |
if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) { | |
return 31; | |
} else if (month == 4 || month == 6 || month == 9 || month == 11) { | |
return 30; | |
} else if (month == 2) { // feb | |
return isLeapYear ? 29 : 28; | |
} else { // emergency | |
return -1; | |
} | |
} | |
function isLeapYear(year) { | |
var leap = (year % 4 == 0); | |
if (year % 100 == 0) { | |
leap = leap && (year % 400 == 0); | |
} | |
return leap; | |
} | |
// https://stackoverflow.com/questions/10073699/pad-a-number-with-leading-zeros-in-javascript | |
function pad(n, width, z) { | |
z = z || '0'; | |
n = n + ''; | |
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; | |
} | |
function updateLevian() { | |
// First, verify the Gregorian date at least looks like a date | |
var gregstr = $("#levcal-gregorian").val(); | |
if (gregstr == "") { | |
defaultmessage(); // don't bother the user if the box is blank | |
return; | |
} else if (!/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(gregstr)) { | |
message("Invalid Gregorian date."); | |
return; | |
} else { | |
defaultmessage(); | |
} | |
// Now split it apart and verify it in more detail | |
var gregdate = gregstr.split("-"); | |
var gregyear = parseInt(gregdate[0]); | |
var gregmonth = parseInt(gregdate[1]); | |
var gregday = parseInt(gregdate[2]); | |
if (gregyear == 0 || // we won't try to handle dates BCE yet | |
gregmonth == 0 || | |
gregmonth > 12 || | |
gregday == 0) { | |
message("Invalid Gregorian date."); | |
return; | |
} | |
// Last verification step: make sure gregday is within bounds for gregmonth | |
var daysingregmonth = daysInMonth(gregmonth, isLeapYear(gregyear)); | |
if (daysingregmonth == -1) { | |
message("Error: couldn't compute number of days in that month."); | |
return; | |
} | |
if (gregday > daysingregmonth) { | |
message("Invalid Gregorian date."); | |
return; | |
} | |
// Now actually compute the Levian date | |
var levyear; | |
var levmonth; | |
var levweek; | |
var levday; | |
// First, compute number of days since 1800-01-01 (epoch) | |
if (gregyear < 1800) { | |
message("Support for dates prior to 1800 is not yet available."); | |
return; | |
} | |
var daysSinceEpoch = 0; | |
for (y = 1800; y < gregyear; y++) { // don't include the year of the input date yet | |
daysSinceEpoch += (isLeapYear(y)) ? 366 : 365; | |
} | |
for (m = 1; m < gregmonth; m++) { | |
daysSinceEpoch += daysInMonth(m, isLeapYear(gregyear)); | |
} | |
daysSinceEpoch += gregday - 1; // -1 since there is no day 0 in any G. (or L.) month | |
var daysLeft = daysSinceEpoch; // renaming to better illustrate its role in the next bit | |
console.log(daysLeft); | |
// Remove days to make a year | |
levyear = Math.floor(daysLeft / 365); | |
daysLeft -= 365 * levyear; | |
levyear += 1; | |
// Remove days to make a month | |
levmonth = Math.floor(daysLeft / 30); | |
daysLeft -= 30 * levmonth; | |
levmonth += 1; | |
// Work out what week of the month it is | |
levweek = Math.floor(daysLeft / 5); | |
daysLeft -= 5 * levweek; | |
levweek += 1; | |
// And the day. | |
levday = daysLeft | |
daysLeft -= levday; | |
levday += 1; | |
// Now make it into a string | |
var levstr = levyear + '/' + levmonth + '/' + levweek + '.' + levday; | |
if (levmonth == 13) { | |
levstr = levyear + '//X.' + levday; | |
} | |
$("#levcal-levian").val(levstr); | |
} | |
function updateGregorian() { | |
// First, verify the Levian date at least looks like a date | |
var levstr = $("#levcal-levian").val(); | |
if (levstr == "") { | |
defaultmessage(); // Don't bother the user if it's blank | |
return; | |
} else if (!/^[0-9]+\/([0-9]+\/[1-6]|\/X)\.[1-5]$/.test(levstr)) { | |
message("Invalid Levian date."); | |
return; | |
} else { | |
defaultmessage(); | |
} | |
// Now split it apart and verify it in more detail | |
var levdate = levstr.split('/'); | |
var levyear = parseInt(levdate[0]); | |
var levmonth; | |
var levweek; | |
var levday = parseInt(levdate[2].split('.')[1]); | |
var levfree = /^[0-9]+\/\/X\.[1-5]$/.test(levstr); | |
if (levfree) { | |
levmonth = 13; | |
levweek = 1; | |
} else { | |
levmonth = parseInt(levdate[1]); | |
levweek = parseInt(levdate[2].split('.')[0]); | |
if (levmonth == 13) { | |
// Not so fast. | |
message("Invalid Levian date."); | |
return; | |
} | |
} | |
if (levmonth < 1 || levmonth > 13 || | |
levweek < 1 || levweek > 6 || | |
levday < 1 || levday > 5 || | |
levmonth == 13 && levweek != 1) { | |
message("Invalid Levian date."); | |
return; | |
} | |
// Now actually compute the Gregorian date | |
// First, convert the Levian date to days since the epoch | |
var daysSinceEpoch = levday - 1; | |
daysSinceEpoch += (levweek - 1) * 5; | |
daysSinceEpoch += (levmonth - 1) * 30; | |
daysSinceEpoch += (levyear - 1) * 365; | |
// Then, start adding those days into a Gregorian date. | |
var gregyear = 1800; | |
var gregmonth = 1; | |
var gregday = 1; | |
// I could divide, too, but this is neater to understand | |
for (daysLeft=daysSinceEpoch; daysLeft > 0; daysLeft--) { | |
gregday += 1; | |
if (gregday > daysInMonth(gregmonth, isLeapYear(gregyear))) { | |
gregday = 1; | |
gregmonth += 1; | |
if (gregmonth > 12) { | |
gregmonth = 1; | |
gregyear += 1; | |
} | |
} | |
} | |
// Lastly, stringify and display the result | |
var gregstr = pad(gregyear, 4) + "-" + pad(gregmonth, 2) + "-" + pad(gregday, 2); | |
$("#levcal-gregorian").val(gregstr); | |
} | |
function reset() { | |
defaultmessage(); | |
$("#levcal-gregorian").val(""); | |
$("#levcal-levian").val(""); | |
} | |
function help() { | |
message("Enter a date in either box and press Enter for magic."); | |
} | |
function init() { | |
$("#levcal").html(widgetcontent); | |
$("#levcal").css("font-weight", "400"); | |
$("#levcal-message").css("font-weight", "600"); | |
$("#levcal-reset").css("float", "right"); | |
$("#levcal-reset").click(reset); | |
$("#levcal-help").css("float", "right"); | |
$("#levcal-help").click(help); | |
$("#levcal-gregorian").change(updateLevian); | |
$("#levcal-levian").change(updateGregorian); | |
defaultmessage(); | |
} | |
init(); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment