Skip to content

Change election date calculations and shipping logic#1

Open
jfsalzmann wants to merge 2 commits intookfde:mainfrom
jfsalzmann:patch-1
Open

Change election date calculations and shipping logic#1
jfsalzmann wants to merge 2 commits intookfde:mainfrom
jfsalzmann:patch-1

Conversation

@jfsalzmann
Copy link

@jfsalzmann jfsalzmann commented Feb 26, 2026

Vorschlag für eine verbesserte Berechnung der voraussichtlichen Zustellzeit der Briefwahlunterlagen.
Basiert weiterhin auf einer Heuristik die es nicht ganz so genau nimmt mit Werktagen, Postlaufzeiten etc., die aber ein paar offensichtliche Fehler behebt:

  • keine vorr. Zustelltage mehr an Sonntagen (Deutsche Post stellt an Samstagen zu)
  • knappere Bestlaufzeit der Unterlagen, dafür Aufschlag wenn am Wochenende beantragt
  • Cutoff-Zeit basierend auf aktueller Uhrzeit bei Beantragung, nach der mit dem nächsten Tag gerechnet wird
  • knappere maximale Bearbeitungszeit (wir geben ja "vorraussichtliche" Zustellung an, meistens stimmt das dann wohl)
  • Zeitraum statt fixer Tag für die Prognose bei sehr früher Beantragung

Im Ergebnis ist die Beantragung den gesamten Montag vor der Wahl noch möglich (bzw. bis Dienstag früh zum Cutoff).

Explizitere Benennung der zeitlichen Annahmen, teilweise sind die Annahmen verändert.

// Simulation of delivery time calculations for BW election (8.3.) for period 5.1.-6.3.

| BEFORE                                               | AFTER (assuming time before cutoff, e.g. 9am)
| date, days until election, from, to, printed period  | date, days until election, from, to, printed period

['5.1.', 61, '6.2.', '6.2.', '6.2.']                   ['5.1.', 61, '6.2.', '13.2.', '6.2. – 13.2.']          
['6.1.', 60, '6.2.', '6.2.', '6.2.']                   ['6.1.', 60, '6.2.', '13.2.', '6.2. – 13.2.']          
['7.1.', 59, '6.2.', '6.2.', '6.2.']                   ['7.1.', 59, '6.2.', '13.2.', '6.2. – 13.2.']          
['8.1.', 58, '6.2.', '6.2.', '6.2.']                   ['8.1.', 58, '6.2.', '13.2.', '6.2. – 13.2.']          
['9.1.', 57, '6.2.', '6.2.', '6.2.']                   ['9.1.', 57, '6.2.', '13.2.', '6.2. – 13.2.']          
['10.1.', 56, '6.2.', '6.2.', '6.2.']                  ['10.1.', 56, '6.2.', '13.2.', '6.2. – 13.2.']         
['11.1.', 55, '6.2.', '6.2.', '6.2.']                  ['11.1.', 55, '6.2.', '13.2.', '6.2. – 13.2.']         
['12.1.', 54, '6.2.', '6.2.', '6.2.']                  ['12.1.', 54, '6.2.', '13.2.', '6.2. – 13.2.']         
['13.1.', 53, '6.2.', '6.2.', '6.2.']                  ['13.1.', 53, '6.2.', '13.2.', '6.2. – 13.2.']         
['14.1.', 52, '6.2.', '6.2.', '6.2.']                  ['14.1.', 52, '6.2.', '13.2.', '6.2. – 13.2.']         
['15.1.', 51, '6.2.', '6.2.', '6.2.']                  ['15.1.', 51, '6.2.', '13.2.', '6.2. – 13.2.']         
['16.1.', 50, '6.2.', '6.2.', '6.2. – 6.2.']           ['16.1.', 50, '6.2.', '13.2.', '6.2. – 13.2.']         
['17.1.', 49, '6.2.', '7.2.', '6.2. – 7.2.']           ['17.1.', 49, '6.2.', '13.2.', '6.2. – 13.2.']         
['18.1.', 48, '6.2.', '8.2.', '6.2. – 8.2.']           ['18.1.', 48, '6.2.', '13.2.', '6.2. – 13.2.']         
['19.1.', 47, '6.2.', '9.2.', '6.2. – 9.2.']           ['19.1.', 47, '6.2.', '13.2.', '6.2. – 13.2.']         
['20.1.', 46, '6.2.', '10.2.', '6.2. – 10.2.']         ['20.1.', 46, '6.2.', '13.2.', '6.2. – 13.2.']         
['21.1.', 45, '6.2.', '11.2.', '6.2. – 11.2.']         ['21.1.', 45, '6.2.', '13.2.', '6.2. – 13.2.']         
['22.1.', 44, '6.2.', '12.2.', '6.2. – 12.2.']         ['22.1.', 44, '6.2.', '13.2.', '6.2. – 13.2.']         
['23.1.', 43, '6.2.', '13.2.', '6.2. – 13.2.']         ['23.1.', 43, '6.2.', '13.2.', '6.2. – 13.2.']         
['24.1.', 42, '6.2.', '14.2.', '6.2. – 14.2.']         ['24.1.', 42, '6.2.', '13.2.', '6.2. – 13.2.']         
['25.1.', 41, '6.2.', '15.2.', '6.2. – 15.2.']         ['25.1.', 41, '6.2.', '13.2.', '6.2. – 13.2.']         
['26.1.', 40, '6.2.', '16.2.', '6.2. – 16.2.']         ['26.1.', 40, '6.2.', '13.2.', '6.2. – 13.2.']         
['27.1.', 39, '6.2.', '17.2.', '6.2. – 17.2.']         ['27.1.', 39, '6.2.', '13.2.', '6.2. – 13.2.']         
['28.1.', 38, '6.2.', '18.2.', '6.2. – 18.2.']         ['28.1.', 38, '6.2.', '13.2.', '6.2. – 13.2.']         
['29.1.', 37, '6.2.', '19.2.', '6.2. – 19.2.']         ['29.1.', 37, '6.2.', '14.2.', '6.2. – 14.2.']         
['30.1.', 36, '6.2.', '20.2.', '6.2. – 20.2.']         ['30.1.', 36, '6.2.', '16.2.', '6.2. – 16.2.']         
['31.1.', 35, '6.2.', '21.2.', '6.2. – 21.2.']         ['31.1.', 35, '6.2.', '16.2.', '6.2. – 16.2.']         
['1.2.', 34, '6.2.', '22.2.', '6.2. – 22.2.']          ['1.2.', 34, '6.2.', '17.2.', '6.2. – 17.2.']          
['2.2.', 33, '6.2.', '23.2.', '6.2. – 23.2.']          ['2.2.', 33, '6.2.', '18.2.', '6.2. – 18.2.']          
['3.2.', 32, '6.2.', '24.2.', '6.2. – 24.2.']          ['3.2.', 32, '6.2.', '19.2.', '6.2. – 19.2.']          
['4.2.', 31, '7.2.', '25.2.', '7.2. – 25.2.']          ['4.2.', 31, '6.2.', '20.2.', '6.2. – 20.2.']          
['5.2.', 30, '8.2.', '26.2.', '8.2. – 26.2.']          ['5.2.', 30, '7.2.', '21.2.', '7.2. – 21.2.']          
['6.2.', 29, '9.2.', '27.2.', '9.2. – 27.2.']          ['6.2.', 29, '9.2.', '23.2.', '9.2. – 23.2.']          
['7.2.', 28, '10.2.', '28.2.', '10.2. – 28.2.']        ['7.2.', 28, '11.2.', '23.2.', '11.2. – 23.2.']        
['8.2.', 27, '11.2.', '1.3.', '11.2. – 1.3.']          ['8.2.', 27, '11.2.', '24.2.', '11.2. – 24.2.']        
['9.2.', 26, '12.2.', '2.3.', '12.2. – 2.3.']          ['9.2.', 26, '11.2.', '25.2.', '11.2. – 25.2.']        
['10.2.', 25, '13.2.', '3.3.', '13.2. – 3.3.']         ['10.2.', 25, '12.2.', '26.2.', '12.2. – 26.2.']       
['11.2.', 24, '14.2.', '4.3.', '14.2. – 4.3.']         ['11.2.', 24, '13.2.', '27.2.', '13.2. – 27.2.']       
['12.2.', 23, '15.2.', '5.3.', '15.2. – 5.3.']         ['12.2.', 23, '14.2.', '28.2.', '14.2. – 28.2.']       
['13.2.', 22, '16.2.', '5.3.', '16.2. – 5.3.']         ['13.2.', 22, '16.2.', '2.3.', '16.2. – 2.3.']         
['14.2.', 21, '17.2.', '5.3.', '17.2. – 5.3.']         ['14.2.', 21, '18.2.', '2.3.', '18.2. – 2.3.']         
['15.2.', 20, '18.2.', '5.3.', '18.2. – 5.3.']         ['15.2.', 20, '18.2.', '3.3.', '18.2. – 3.3.']         
['16.2.', 19, '19.2.', '5.3.', '19.2. – 5.3.']         ['16.2.', 19, '18.2.', '4.3.', '18.2. – 4.3.']         
['17.2.', 18, '20.2.', '5.3.', '20.2. – 5.3.']         ['17.2.', 18, '19.2.', '5.3.', '19.2. – 5.3.']         
['18.2.', 17, '21.2.', '5.3.', '21.2. – 5.3.']         ['18.2.', 17, '20.2.', '5.3.', '20.2. – 5.3.']         
['19.2.', 16, '22.2.', '5.3.', '22.2. – 5.3.']         ['19.2.', 16, '21.2.', '5.3.', '21.2. – 5.3.']         
['20.2.', 15, '23.2.', '5.3.', '23.2. – 5.3.']         ['20.2.', 15, '23.2.', '5.3.', '23.2. – 5.3.']         
['21.2.', 14, '24.2.', '5.3.', '24.2. – 5.3.']         ['21.2.', 14, '25.2.', '5.3.', '25.2. – 5.3.']         
['22.2.', 13, '25.2.', '5.3.', '25.2. – 5.3.']         ['22.2.', 13, '25.2.', '5.3.', '25.2. – 5.3.']         
['23.2.', 12, '26.2.', '5.3.', '26.2. – 5.3.']         ['23.2.', 12, '25.2.', '5.3.', '25.2. – 5.3.']         
['24.2.', 11, '27.2.', '5.3.', '27.2. – 5.3.']         ['24.2.', 11, '26.2.', '5.3.', '26.2. – 5.3.']         
['25.2.', 10, '28.2.', '5.3.', '28.2. – 5.3.']         ['25.2.', 10, '27.2.', '5.3.', '27.2. – 5.3.']         
['26.2.', 9, '1.3.', '5.3.', '1.3. – 5.3.']            ['26.2.', 9, '28.2.', '5.3.', '28.2. – 5.3.']          
['27.2.', 8, '2.3.', '5.3.', '2.3. – 5.3.']            ['27.2.', 8, '2.3.', '5.3.', '2.3. – 5.3.']            
['28.2.', 7, '3.3.', '5.3.', '3.3. – 5.3.']            ['28.2.', 7, '4.3.', '5.3.', '4.3. – 5.3.']            
['1.3.', 6, '4.3.', '5.3.', '4.3. – 5.3.']             ['1.3.', 6, '4.3.', '5.3.', '4.3. – 5.3.']             
['2.3.', 5, '5.3.', '5.3.', '5.3.'] // too late        ['2.3.', 5, '4.3.', '5.3.', '4.3. – 5.3.']             
['3.3.', 4, '6.3.', '6.3.', '6.3.'] // too late        ['3.3.', 4, '5.3.', '5.3.', '5.3.']                    
['4.3.', 3, '7.3.', '7.3.', '7.3.'] // too late        ['4.3.', 3, '6.3.', '6.3.', '6.3.'] // too late        
['5.3.', 2, '8.3.', '8.3.', '8.3.'] // too late        ['5.3.', 2, '7.3.', '7.3.', '7.3.'] // too late        
['6.3.', 1, '9.3.', '9.3.', '9.3.'] // too late        ['6.3.', 1, '9.3.', '9.3.', '9.3.'] // too late        

@jfsalzmann jfsalzmann marked this pull request as ready for review February 26, 2026 20:14
@jfsalzmann
Copy link
Author

code for simulation

// AFTER

function sim(ndays){
const CUTOFF_HOUR = 24 // //////////////////// ignore cutoff for simulation
const MIN_DAYS_SHIPPING = 2
const MIN_DAYS_RETURN = 3
const MIN_DAYS_BALLOTS_READY_BEFORE_ELECTION = 30
const MIN_DAYS_ORDER_BEFORE_ELECTION = 6
const MAX_DAYS_SHIPMENT_PERIOD = 7
const MAX_DAYS_PROCESSING = 14
const MAX_DAYS_SHIPPING = MIN_DAYS_SHIPPING + MAX_DAYS_PROCESSING

const day = 1000 * 60 * 60 * 24
const todayDate = new Date()
const today = todayDate.getTime() + (todayDate.getHours() >= CUTOFF_HOUR ? day : 0) + (-ndays*day)
const electionTime = selectedElection.date.getTime()
const daysLeft = Math.floor((electionTime - today) / day)

const shiftSundays = d => d + (new Date(d).getDay() === 0 ? day : 0)
const shiftWeekends = d => shiftSundays(d) + (new Date(shiftSundays(d)).getDay() === 6 ? (2 * day) : 0)
const shippingDateMin = shiftSundays(
Math.max(
  shiftWeekends(today) + (MIN_DAYS_SHIPPING * day), electionTime - (MIN_DAYS_BALLOTS_READY_BEFORE_ELECTION * day)
)
)
const shippingDateMax = shiftSundays(
Math.max(
  shippingDateMin, Math.min(
    electionTime - (MIN_DAYS_RETURN * day), Math.max(
      shippingDateMin + (MAX_DAYS_SHIPMENT_PERIOD * day), today + (MAX_DAYS_SHIPPING * day)
    )
  )
)
)

const arrival = (shippingDateMin === shippingDateMax) ?
`${formatDate(shippingDateMin)}` :
`${formatDate(shippingDateMin)}${formatDate(shippingDateMax)}`
//document.getElementById("arrival-date").innerText = arrival

return([formatDate(today),daysLeft,formatDate(shippingDateMin),formatDate(shippingDateMax),arrival]);
}
for(let i = -52; i <= 8; i++){
console.log(sim(-i));
}
// BEFORE

function sim(ndays){
const MIN_DAYS = 6
const MIN_SHIPPING_DAYS = 3
const day = 1000 * 60 * 60 * 24
const todayDate = new Date() 
const today = todayDate.getTime() + (-ndays*day)
const electionTime = selectedElection.date.getTime()
const shippingDateMin = Math.max(today + (3 * day), electionTime - (30 * day))
const shippingDateMax = Math.max(shippingDateMin, Math.min(today + (21 * day), electionTime - (MIN_SHIPPING_DAYS * day)))
const daysLeft = Math.floor((electionTime - today) / day)


const arrival = (shippingDateMin === shippingDateMax) ?
`${formatDate(shippingDateMin)}` :
`${formatDate(shippingDateMin)}${formatDate(shippingDateMax)}`
//document.getElementById("arrival-date").innerText = arrival

return([formatDate(today),daysLeft,formatDate(shippingDateMin),formatDate(shippingDateMax),arrival]);
}
for(let i = -52; i <= 8; i++){
console.log(sim(-i));
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant