const TUESDAY = 2 const THURSDAY = 4 const WEEK = 7 // for easier mocking in tests var today = new Date() export default function NextTopic() { // don't put the nextTopic date in the staticly generated html // because it would be outdated rather quickly const isSSR = typeof window === "undefined" if (isSSR) { test() return "unbekannt" } return formatDateInfo(getNextTopicDate()) } // javascript dates are not nice function getNextTopicDate() { // first thursday and third tuesday in month const nextTopic = zeroizeTime(today) // first thursday if (calculatePriorWeekdays(THURSDAY) === 0) { addDays(nextTopic, getDaysUntilNext(THURSDAY, nextTopic)) return nextTopic } // third tuesday const priorTuesdays = calculatePriorWeekdays(TUESDAY) if (priorTuesdays <= 2) { addDays(nextTopic, getDaysUntilNext(TUESDAY, nextTopic)) addDays(nextTopic, WEEK * (2 - priorTuesdays)) return nextTopic } // first thursday next month const currentMonth = today.getMonth() addDays(nextTopic, getDaysUntilNext(THURSDAY, nextTopic)) while (nextTopic.getMonth() === currentMonth) { addDays(nextTopic, WEEK) } return nextTopic } /** * calculate how many of the given weekday this month already had. * for example: how many tuesdays were in this month already */ function calculatePriorWeekdays(weekday) { const testDate = new Date(today) testDate.setDate(1) var priorWeekdays = 0 while (testDate < today) { if (testDate.getDay() === weekday) { priorWeekdays++ } testDate.setDate(testDate.getDate() + 1) } return priorWeekdays } /** * how many days are there until the next starting from */ function getDaysUntilNext(weekday, date) { return mod(weekday - date.getDay(), WEEK) } /** * just the modulo function, but always return the positive result */ function mod(n, m) { return ((n % m) + m) % m } /** * add days to the * but do it in a way that ignores daylight savings time */ function addDays(date, days) { date.setDate(date.getDate() + days) if (date.getHours() > 12) { date.setDate(date.getDate() + 1) } else if (date.getHours() !== 0) { date.setDate(date.getDate() - 1) } } /** * return a human readable representation of the date */ function formatDateInfo(date) { const dayNames = { "2": "Dienstag", "4": "Donnerstag", } const dayName = dayNames[date.getDay()] const isoDate = getISODateString(date) const weeks = weeksBetween(today, date) if (weeks === 0 && date.getDay() === today.getDay()) { return `Heute, ${isoDate}` } else if (weeks === 0) { return `Diese Woche ${dayName}, ${isoDate}` } else if (weeks === 1) { return `Nächste Woche ${dayName}, ${isoDate}` } else { return `${dayName} in ${weeks} Wochen, ${isoDate}` } } /** * how many sunday to monday transitions are between the two dates */ function weeksBetween(datetime1, datetime2) { const date1 = zeroizeTime(datetime1) const date2 = zeroizeTime(datetime2) const MILLISECONDS_IN_WEEK = 7 * 24 * 60 * 60 * 1000 var weeks = Math.floor((date2 - date1) / MILLISECONDS_IN_WEEK) // if there is a sunday to monday transition between if (mod(date1.getDay() - 1, WEEK) > mod(date2.getDay() - 1, WEEK)) { weeks += 1 } return weeks } function getISODateString(date) { const year = date.getFullYear() const month = date.getMonth() + 1 const day = date.getDate() const monthPadded = (month < 10 ? "0" : "") + month const dayPadded = (day < 10 ? "0" : "") + day return `${year}-${monthPadded}-${dayPadded}` } function zeroizeTime(date) { const copy = new Date(date) copy.setHours(0) copy.setMinutes(0) copy.setSeconds(0) copy.setMilliseconds(0) return copy } // test, because this is complicated function test() { testLateSunday() testYear2020() // reset to correct value today = new Date() } function testLateSunday() { today = new Date("2020-01-19T23:59:59+01:00") const result = formatDateInfo(getNextTopicDate()) console.assert( result === "Nächste Woche Dienstag, 2020-01-21", `starting at ${getISODateString( today )}: was ${result}, expected "Nächste Woche Dienstag, 2020-01-21"` ) } function testYear2020() { const topicsIn2020 = [ "2020-01-02", "2020-01-21", "2020-02-06", "2020-02-18", "2020-03-05", "2020-03-17", "2020-04-02", "2020-04-21", "2020-05-07", "2020-05-19", "2020-06-04", "2020-06-16", "2020-07-02", "2020-07-21", "2020-08-06", "2020-08-18", "2020-09-03", "2020-09-15", "2020-10-01", "2020-10-20", "2020-11-05", "2020-11-17", "2020-12-03", "2020-12-15", ] today = zeroizeTime(new Date("2020-01-01")) let currentIndex = 0 while (today <= new Date("2020-12-15")) { const result = getISODateString(getNextTopicDate()) const expect = topicsIn2020[currentIndex] console.assert( result === expect, `starting at ${getISODateString( today )}: was ${result}, expected ${expect}` ) if (getISODateString(today) === result) { currentIndex++ } addDays(today, 1) } }