219 lines
5.2 KiB
JavaScript
219 lines
5.2 KiB
JavaScript
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 '<Benötigt JavaScript>'
|
|
}
|
|
|
|
return formatDateInfo(getNextTopicDate())
|
|
}
|
|
|
|
// javascript dates are not nice
|
|
|
|
function getNextTopicDate() {
|
|
// first thursday and third tuesday in month
|
|
const nextTopic = zeroizeTime(today)
|
|
|
|
nextTopic.setMonth(5)
|
|
nextTopic.setDate(1)
|
|
|
|
// first thursday
|
|
if (calculatePriorWeekdays(nextTopic, THURSDAY) === 0) {
|
|
addDays(nextTopic, getDaysUntilNext(THURSDAY, nextTopic))
|
|
return nextTopic
|
|
}
|
|
// third tuesday
|
|
const priorTuesdays = calculatePriorWeekdays(nextTopic, 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(date, weekday) {
|
|
const testDate = new Date(date)
|
|
testDate.setDate(1)
|
|
|
|
var priorWeekdays = 0
|
|
while (testDate < date) {
|
|
if (testDate.getDay() === weekday) {
|
|
priorWeekdays++
|
|
}
|
|
testDate.setDate(testDate.getDate() + 1)
|
|
}
|
|
|
|
return priorWeekdays
|
|
}
|
|
|
|
/**
|
|
* how many days are there until the next <weekday> starting from <date>
|
|
*/
|
|
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> days to the <date>
|
|
* 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)
|
|
}
|
|
}
|