diff --git a/src/components/nextTopic.js b/src/components/nextTopic.js new file mode 100644 index 0000000..4d25cf1 --- /dev/null +++ b/src/components/nextTopic.js @@ -0,0 +1,188 @@ +const TUESDAY = 2 +const THURSDAY = 4 +const WEEK = 7 + +// for easier mocking in tests +var today = new Date() + +export default () => { + // 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) { + testNextTopic() + return "unbekannt" + } + + return formatDateInfo(getNextTopicDate()) +} + +// javascript dates are not nice + +function getNextTopicDate() { + // first thursday and third tuesday in month + const nextTopic = new Date(today) + nextTopic.setHours(0) + nextTopic.setMinutes(0) + nextTopic.setSeconds(0) + nextTopic.setMilliseconds(0) + + // 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 daysTillTuesday + */ +function weeksBetween(date1, date2) { + 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}` +} + +// test, becuase this is complicated + +function testNextTopic() { + 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 = new Date("2020-01-01") + + for (const nextTopic of topicsIn2020) { + const result = getISODateString(getNextTopicDate()) + console.assert( + result === nextTopic, + `starting at ${getISODateString( + today + )}: was ${result}, expected ${nextTopic}` + ) + today = new Date(result) + today.setDate(today.getDate() + 1) + } + + // reset to correct value + today = new Date() +} diff --git a/src/markdown-pages/events.mdx b/src/markdown-pages/events.mdx index 9be7021..593ddbe 100644 --- a/src/markdown-pages/events.mdx +++ b/src/markdown-pages/events.mdx @@ -3,5 +3,8 @@ path: "/events" title: "events" edit: "events.mdx" --- +import NextTopic from "../components/nextTopic.js" # Events + +**Nächstes Topic-Treff**: