diff --git a/95_Weekday/javascript/weekday.js b/95_Weekday/javascript/weekday.js index 276e1817..6070c71c 100644 --- a/95_Weekday/javascript/weekday.js +++ b/95_Weekday/javascript/weekday.js @@ -3,83 +3,345 @@ // Converted from BASIC to Javascript by Oscar Toledo G. (nanochess) // -function print(str) -{ +/** + * Print given string to the end of the "output" element. + * @param str + */ +function print(str) { document.getElementById("output").appendChild(document.createTextNode(str)); } -function input() -{ - var input_element; - var input_str; - +/** + * Obtain user input + * @returns {Promise} + */ +function input() { return new Promise(function (resolve) { - input_element = document.createElement("INPUT"); - - print("? "); - input_element.setAttribute("type", "text"); - input_element.setAttribute("length", "50"); - document.getElementById("output").appendChild(input_element); - input_element.focus(); - input_str = undefined; - input_element.addEventListener("keydown", function (event) { - if (event.keyCode == 13) { - input_str = input_element.value; - document.getElementById("output").removeChild(input_element); - print(input_str); - print("\n"); - resolve(input_str); - } - }); - }); + const input_element = document.createElement("INPUT"); + + print("? "); + input_element.setAttribute("type", "text"); + input_element.setAttribute("length", "50"); + document.getElementById("output").appendChild(input_element); + input_element.focus(); + input_element.addEventListener("keydown", function (event) { + if (event.keyCode === 13) { + const input_str = input_element.value; + document.getElementById("output").removeChild(input_element); + print(input_str); + print("\n"); + resolve(input_str); + } + }); + }); } -function tab(space) -{ - var str = ""; - while (space-- > 0) +/** + * Create a string consisting of the given number of spaces + * @param spaceCount + * @returns {string} + */ +function tab(spaceCount) { + let str = ""; + while (spaceCount-- > 0) str += " "; return str; } -function fna(arg) { - return Math.floor(arg / 4); +const MONTHS_PER_YEAR = 12; +const DAYS_PER_COMMON_YEAR = 365; +const DAYS_PER_IDEALISED_MONTH = 30; +const MAXIMUM_DAYS_PER_MONTH = 31; +// In a common (non-leap) year the day of the week for the first of each month moves by the following amounts. +const COMMON_YEAR_MONTH_OFFSET = [0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5]; + +/** + * Date representation. + */ +class DateStruct { + #year; + #month; + #day; + + /** + * Build a DateStruct + * @param {number} year + * @param {number} month + * @param {number} day + */ + constructor(year, month, day) { + this.#year = year; + this.#month = month; + this.#day = day; + } + + get year() { + return this.#year; + } + + get month() { + return this.#month; + } + + get day() { + return this.#day; + } + + /** + * Determine if the date could be a Gregorian date. + * Be aware the Gregorian calendar was not introduced in all places at once, + * see https://en.wikipedia.org/wiki/Gregorian_calendar + * @returns {boolean} true if date could be Gregorian; otherwise false. + */ + isGregorianDate() { + let result = false; + if (this.#year > 1582) { + result = true; + } else if (this.#year === 1582) { + if (this.#month > 10) { + result = true; + } else if (this.#month === 10 && this.#day >= 15) { + result = true; + } + } + return result; + } + + /** + * The following performs a hash on the day parts which guarantees that + * 1. different days will return different numbers + * 2. the numbers returned are ordered. + * @returns {number} + */ + getNormalisedDay() { + return (this.year * MONTHS_PER_YEAR + this.month) * MAXIMUM_DAYS_PER_MONTH + this.day; + } + + /** + * Determine the day of the week. + * This calculation returns a number between 1 and 7 where Sunday=1, Monday=2, ..., Saturday=7. + * @returns {number} Value between 1 and 7 representing Sunday to Saturday. + */ + getDayOfWeek() { + // Calculate an offset based on the century part of the year. + const centuriesSince1500 = Math.floor((this.year - 1500) / 100); + let centuryOffset = centuriesSince1500 * 5 + (centuriesSince1500 + 3) / 4; + centuryOffset = Math.floor(centuryOffset % 7); + + // Calculate an offset based on the shortened two digit year. + // January 1st moves forward by approximately 1.25 days per year + const yearInCentury = this.year % 100; + const yearInCenturyOffsets = yearInCentury / 4 + yearInCentury; + + // combine offsets with day and month + let dayOfWeek = centuryOffset + yearInCenturyOffsets + this.day + COMMON_YEAR_MONTH_OFFSET[this.month - 1]; + + dayOfWeek = Math.floor(dayOfWeek % 7) + 1; + if (this.month <= 2 && this.isLeapYear()) { + dayOfWeek--; + } + if (dayOfWeek === 0) { + dayOfWeek = 7; + } + return dayOfWeek; + } + + /** + * Determine if the given year is a leap year. + * @returns {boolean} + */ + isLeapYear() { + if ((this.year % 4) !== 0) { + return false; + } else if ((this.year % 100) !== 0) { + return true; + } else if ((this.year % 400) !== 0) { + return false; + } + return true; + } + + /** + * Returns a US formatted date, i.e. Month/Day/Year. + * @returns {string} + */ + toString() { + return this.#month + "/" + this.#day + "/" + this.#year; + } } -function fnb(arg) { - return Math.floor(arg / 7); -} +/** + * Duration representation. + * Note: this class only handles positive durations well + */ +class Duration { + #years; + #months; + #days; -var t = [, 0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5]; - -var k5; -var k6; -var k7; - -function time_spent(f, a8) -{ - k1 = Math.floor(f * a8); - i5 = Math.floor(k1 / 365); - k1 -= i5 * 365; - i6 = Math.floor(k1 / 30); - i7 = k1 - (i6 * 30); - k5 -= i5; - k6 -= i6; - k7 -= i7; - if (k7 < 0) { - k7 += 30; - k6--; + /** + * Build a Duration + * @param {number} years + * @param {number} months + * @param {number} days + */ + constructor(years, months, days) { + this.#years = years; + this.#months = months; + this.#days = days; + this.#fixRanges(); } - if (k6 <= 0) { - k6 += 12; - k5--; + + get years() { + return this.#years; + } + + get months() { + return this.#months; + } + + get days() { + return this.#days; + } + + clone() { + return new Duration(this.#years, this.#months, this.#days); + } + + /** + * Adjust Duration by removing years, months and days from supplied Duration. + * This is a naive calculation which assumes all months are 30 days. + * @param {Duration} timeToRemove + */ + remove(timeToRemove) { + this.#years -= timeToRemove.years; + this.#months -= timeToRemove.months; + this.#days -= timeToRemove.days; + this.#fixRanges(); + } + + /** + * Move days and months into expected range. + */ + #fixRanges() { + if (this.#days < 0) { + this.#days += DAYS_PER_IDEALISED_MONTH; + this.#months--; + } + if (this.#months < 0) { + this.#months += MONTHS_PER_YEAR; + this.#years--; + } + } + + /** + * Computes an approximation of the days covered by the duration. + * The calculation assumes all years are 365 days, months are 30 days each, + * and adds on an extra bit the more months that have passed. + * @returns {number} + */ + getApproximateDays() { + return ( + (this.#years * DAYS_PER_COMMON_YEAR) + + (this.#months * DAYS_PER_IDEALISED_MONTH) + + this.#days + + Math.floor(this.#months / 2) + ); + } + + /** + * Returns a formatted duration with tab separated values, i.e. Years\tMonths\tDays. + * @returns {string} + */ + toString() { + return this.#years + "\t" + this.#months + "\t" + this.#days; + } + + /** + * Determine approximate Duration between two dates. + * This is a naive calculation which assumes all months are 30 days. + * @param {DateStruct} date1 + * @param {DateStruct} date2 + * @returns {Duration} + */ + static between(date1, date2) { + let years = date1.year - date2.year; + let months = date1.month - date2.month; + let days = date1.day - date2.day; + return new Duration(years, months, days); + } + + /** + * Calculate years, months and days as factor of days. + * This is a naive calculation which assumes all months are 30 days. + * @param dayCount Total day to convert to a duration + * @param factor Factor to apply when calculating the duration + * @returns {Duration} + */ + static fromDays(dayCount, factor) { + let totalDays = Math.floor(factor * dayCount); + const years = Math.floor(totalDays / DAYS_PER_COMMON_YEAR); + totalDays -= years * DAYS_PER_COMMON_YEAR; + const months = Math.floor(totalDays / DAYS_PER_IDEALISED_MONTH); + const days = totalDays - (months * DAYS_PER_IDEALISED_MONTH); + return new Duration(years, months, days); } - print(i5 + "\t" + i6 + "\t" + i7 + "\n"); } // Main control section -async function main() -{ +async function main() { + /** + * Reads a date, and extracts the date information. + * This expects date parts to be comma separated, using US date ordering, + * i.e. Month,Day,Year. + * @returns {Promise} + */ + async function inputDate() { + let dateString = await input(); + const month = parseInt(dateString); + const day = parseInt(dateString.substr(dateString.indexOf(",") + 1)); + const year = parseInt(dateString.substr(dateString.lastIndexOf(",") + 1)); + return new DateStruct(year, month, day); + } + + /** + * Obtain text for the day of the week. + * @param {DateStruct} date + * @returns {string} + */ + function getDayOfWeekText(date) { + const dayOfWeek = date.getDayOfWeek(); + let dayOfWeekText = ""; + switch (dayOfWeek) { + case 1: + dayOfWeekText = "SUNDAY."; + break; + case 2: + dayOfWeekText = "MONDAY."; + break; + case 3: + dayOfWeekText = "TUESDAY."; + break; + case 4: + dayOfWeekText = "WEDNESDAY."; + break; + case 5: + dayOfWeekText = "THURSDAY."; + break; + case 6: + if (date.day === 13) { + dayOfWeekText = "FRIDAY THE THIRTEENTH---BEWARE!"; + } else { + dayOfWeekText = "FRIDAY."; + } + break; + case 7: + dayOfWeekText = "SATURDAY."; + break; + } + return dayOfWeekText; + } + print(tab(32) + "WEEKDAY\n"); print(tab(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n"); print("\n"); @@ -89,111 +351,69 @@ async function main() print("GIVES FACTS ABOUT A DATE OF INTEREST TO YOU.\n"); print("\n"); print("ENTER TODAY'S DATE IN THE FORM: 3,24,1979 "); - str = await input(); - m1 = parseInt(str); - d1 = parseInt(str.substr(str.indexOf(",") + 1)); - y1 = parseInt(str.substr(str.lastIndexOf(",") + 1)); + const today = await inputDate(); // This program determines the day of the week // for a date after 1582 print("ENTER DAY OF BIRTH (OR OTHER DAY OF INTEREST)"); - str = await input(); - m = parseInt(str); - d = parseInt(str.substr(str.indexOf(",") + 1)); - y = parseInt(str.substr(str.lastIndexOf(",") + 1)); + const dateOfBirth = await inputDate(); print("\n"); - i1 = Math.floor((y - 1500) / 100); // Test for date before current calendar. - if (y - 1582 < 0) { - print("NOT PREPARED TO GIVE DAY OF WEEK PRIOR TO MDLXXXII.\n"); + if (!dateOfBirth.isGregorianDate()) { + print("NOT PREPARED TO GIVE DAY OF WEEK PRIOR TO X.XV.MDLXXXII.\n"); } else { - a = i1 * 5 + (i1 + 3) / 4; - i2 = Math.floor(a - fnb(a) * 7); - y2 = Math.floor(y / 100); - y3 = Math.floor(y - y2 * 100); - a = y3 / 4 + y3 + d + t[m] + i2; - b = Math.floor(a - fnb(a) * 7) + 1; - if (m <= 2) { - if (y3 != 0) { - t1 = Math.floor(y - fna(y) * 4); - } else { - a = i1 - 1; - t1 = Math.floor(a - fna(a) * 4); - } - if (t1 == 0) { - if (b == 0) - b = 6; - b--; - } - } - if (b == 0) - b = 7; - if ((y1 * 12 + m1) * 31 + d1 < (y * 12 + m) * 31 + d) { - print(m + "/" + d + "/" + y + " WILL BE A "); - } else if ((y1 * 12 + m1) * 31 + d1 == (y * 12 + m) * 31 + d) { - print(m + "/" + d + "/" + y + " IS A "); + const normalisedToday = today.getNormalisedDay(); + const normalisedDob = dateOfBirth.getNormalisedDay(); + + let dayOfWeekText = getDayOfWeekText(dateOfBirth); + if (normalisedToday < normalisedDob) { + print(dateOfBirth + " WILL BE A " + dayOfWeekText + "\n"); + } else if (normalisedToday === normalisedDob) { + print(dateOfBirth + " IS A " + dayOfWeekText + "\n"); } else { - print(m + "/" + d + "/" + y + " WAS A "); + print(dateOfBirth + " WAS A " + dayOfWeekText + "\n"); } - switch (b) { - case 1: print("SUNDAY.\n"); break; - case 2: print("MONDAY.\n"); break; - case 3: print("TUESDAY.\n"); break; - case 4: print("WEDNESDAY.\n"); break; - case 5: print("THURSDAY.\n"); break; - case 6: - if (d == 13) { - print("FRIDAY THE THIRTEENTH---BEWARE!\n"); - } else { - print("FRIDAY.\n"); - } - break; - case 7: print("SATURDAY.\n"); break; - } - if ((y1 * 12 + m1) * 31 + d1 != (y * 12 + m) * 31 + d) { - i5 = y1 - y; + + if (normalisedToday !== normalisedDob) { print("\n"); - i6 = m1 - m; - i7 = d1 - d; - if (i7 < 0) { - i6--; - i7 += 30; - } - if (i6 < 0) { - i5--; - i6 += 12; - } - if (i5 >= 0) { - if (i7 == 0 && i6 == 0) + let differenceBetweenDates = Duration.between(today, dateOfBirth); + if (differenceBetweenDates.years >= 0) { + if (differenceBetweenDates.days === 0 && differenceBetweenDates.months === 0) { print("***HAPPY BIRTHDAY***\n"); + } print(" \tYEARS\tMONTHS\tDAYS\n"); print(" \t-----\t------\t----\n"); - print("YOUR AGE (IF BIRTHDATE) \t" + i5 + "\t" + i6 + "\t" + i7 + "\n"); - a8 = (i5 * 365) + (i6 * 30) + i7 + Math.floor(i6 / 2); - k5 = i5; - k6 = i6; - k7 = i7; - // Calculate retirement date. - e = y + 65; - // Calculate time spent in the following functions. - print("YOU HAVE SLEPT \t\t\t"); - time_spent(0.35, a8); - print("YOU HAVE EATEN \t\t\t"); - time_spent(0.17, a8); - if (k5 <= 3) { - print("YOU HAVE PLAYED \t\t\t"); - } else if (k5 <= 9) { - print("YOU HAVE PLAYED/STUDIED \t\t"); + print("YOUR AGE (IF BIRTHDATE) \t" + differenceBetweenDates + "\n"); + + const approximateDaysBetween = differenceBetweenDates.getApproximateDays(); + const unaccountedTime = differenceBetweenDates.clone(); + + // 35% sleeping + const sleepTimeSpent = Duration.fromDays(approximateDaysBetween, 0.35); + print("YOU HAVE SLEPT \t\t\t" + sleepTimeSpent + "\n"); + unaccountedTime.remove(sleepTimeSpent); + + // 17% eating + const eatenTimeSpent = Duration.fromDays(approximateDaysBetween, 0.17); + print("YOU HAVE EATEN \t\t\t" + eatenTimeSpent + "\n"); + unaccountedTime.remove(eatenTimeSpent); + + // 23% working, studying or playing + const workPlayTimeSpent = Duration.fromDays(approximateDaysBetween, 0.23); + if (unaccountedTime.years <= 3) { + print("YOU HAVE PLAYED \t\t" + workPlayTimeSpent + "\n"); + } else if (unaccountedTime.years <= 9) { + print("YOU HAVE PLAYED/STUDIED \t" + workPlayTimeSpent + "\n"); } else { - print("YOU HAVE WORKED/PLAYED \t\t"); + print("YOU HAVE WORKED/PLAYED \t\t" + workPlayTimeSpent + "\n"); } - time_spent(0.23, a8); - if (k6 == 12) { - k5++; - k6 = 0; - } - print("YOU HAVE RELAXED \t\t" + k5 + "\t" + k6 + "\t" + k7 + "\n"); + unaccountedTime.remove(workPlayTimeSpent); + + // Remaining time spent relaxing + print("YOU HAVE RELAXED \t\t" + unaccountedTime + "\n"); + + const retirementYear = dateOfBirth.year + 65; print("\n"); - print(tab(16) + "*** YOU MAY RETIRE IN " + e + " ***\n"); + print(tab(16) + "*** YOU MAY RETIRE IN " + retirementYear + " ***\n"); print("\n"); } }