diff options
author | ByteHamster <ByteHamster@users.noreply.github.com> | 2024-02-25 14:01:03 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-25 14:01:03 +0100 |
commit | 82c93bf7ee3d91395533068fdfe640dfa53113fe (patch) | |
tree | 83beaea8826b94adc181b9f3643e3b6a2c2fe603 /core/src/test/java/de/danoeh/antennapod | |
parent | ef4af0d29d6742fe07e32971b55a1236f8ad08ab (diff) | |
download | AntennaPod-82c93bf7ee3d91395533068fdfe640dfa53113fe.zip |
Guess next episode release date (#6925)
Diffstat (limited to 'core/src/test/java/de/danoeh/antennapod')
-rw-r--r-- | core/src/test/java/de/danoeh/antennapod/core/util/ReleaseScheduleGuesserRealWorldTest.java | 125 | ||||
-rw-r--r-- | core/src/test/java/de/danoeh/antennapod/core/util/ReleaseScheduleGuesserTest.java | 172 |
2 files changed, 297 insertions, 0 deletions
diff --git a/core/src/test/java/de/danoeh/antennapod/core/util/ReleaseScheduleGuesserRealWorldTest.java b/core/src/test/java/de/danoeh/antennapod/core/util/ReleaseScheduleGuesserRealWorldTest.java new file mode 100644 index 000000000..dd3cd763b --- /dev/null +++ b/core/src/test/java/de/danoeh/antennapod/core/util/ReleaseScheduleGuesserRealWorldTest.java @@ -0,0 +1,125 @@ +package de.danoeh.antennapod.core.util; + +import de.danoeh.antennapod.parser.feed.util.DateUtils; +import org.apache.commons.io.IOUtils; +import org.junit.Test; + +import java.io.InputStream; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.Locale; + +import static org.junit.Assert.assertTrue; + +public class ReleaseScheduleGuesserRealWorldTest { + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ROOT); + + private void printHistogram(int[] histogram) { + int max = 0; + for (int x : histogram) { + max = Math.max(x, max); + } + for (int row = 8; row >= 0; row--) { + for (int x : histogram) { + System.out.print((x > (double) row * (max / 9.0)) ? "#" : " "); + } + System.out.println(); + } + for (int col = 0; col < histogram.length; col++) { + System.out.print(((col % 5) == 0) ? "|" : " "); + } + System.out.println(); + } + + @Test + public void testRealWorld() throws Exception { + InputStream inputStream = getClass().getClassLoader().getResource("release_dates.csv").openStream(); + int numCorrectDay = 0; + int numFoundSchedule = 0; + int numFoundScheduleAndCorrectDay = 0; + int num3hoursCorrect = 0; + int numOffByMoreThan2days = 0; + int[] histogram = new int[101]; + + String csv = IOUtils.toString(inputStream, "UTF-8"); + String[] lines = csv.split("\n"); + int totalPodcasts = 0; + int lineNr = 0; + for (String line : lines) { + lineNr++; + String[] dates = line.split(";"); + List<Date> releaseDates = new ArrayList<>(); + for (String date : dates) { + releaseDates.add(DateUtils.parse(date)); + } + if (releaseDates.size() <= 3) { + continue; + } + totalPodcasts++; + Collections.sort(releaseDates, Comparator.comparingLong(Date::getTime)); + Date dateActual = releaseDates.get(releaseDates.size() - 1); + // Remove most recent one and possible duplicates of episodes on the same day + do { + releaseDates = releaseDates.subList(0, releaseDates.size() - 1); + } while (releaseDates.get(releaseDates.size() - 1).getTime() + > dateActual.getTime() - 30 * ReleaseScheduleGuesser.ONE_MINUTE); + ReleaseScheduleGuesser.Guess guess = ReleaseScheduleGuesser.performGuess(releaseDates); + + final boolean is3hoursClose = Math.abs(dateActual.getTime() - guess.nextExpectedDate.getTime()) + < 3 * ReleaseScheduleGuesser.ONE_HOUR; + System.out.println(lineNr + " guessed: " + DATE_FORMAT.format(guess.nextExpectedDate) + + ", actual: " + DATE_FORMAT.format(dateActual) + + " " + guess.schedule.name() + (is3hoursClose ? " ✔" : "")); + long deltaTime = dateActual.getTime() - guess.nextExpectedDate.getTime(); + int histogramClass = (int) Math.max(0, Math.min(100, deltaTime / ReleaseScheduleGuesser.ONE_HOUR + 50)); + histogram[histogramClass]++; + boolean foundSchedule = guess.schedule != ReleaseScheduleGuesser.Schedule.UNKNOWN; + if (foundSchedule) { + numFoundSchedule++; + } + Calendar calendarExpected = new GregorianCalendar(); + calendarExpected.setTime(dateActual); + Calendar calendarGuessed = new GregorianCalendar(); + calendarGuessed.setTime(guess.nextExpectedDate); + if (calendarExpected.get(Calendar.DAY_OF_YEAR) == calendarGuessed.get(Calendar.DAY_OF_YEAR)) { + numCorrectDay++; + if (foundSchedule) { + numFoundScheduleAndCorrectDay++; + } + } + if (Math.abs(deltaTime) > 2 * ReleaseScheduleGuesser.ONE_DAY) { + numOffByMoreThan2days++; + } + if (is3hoursClose) { + num3hoursCorrect++; + } + } + + System.out.println("Podcasts tested: " + totalPodcasts); + + double schedulePercentage = 100.0 * numFoundSchedule / totalPodcasts; + System.out.println("Found schedule: " + schedulePercentage); + double offByLessThan3HoursPercentage = 100.0 * num3hoursCorrect / totalPodcasts; + System.out.println("Off by less than 3 hours: " + offByLessThan3HoursPercentage); + double scheduleAndCorrectDayPercentage = 100.0 * numFoundScheduleAndCorrectDay / numFoundSchedule; + System.out.println("Correct day when schedule found: " + scheduleAndCorrectDayPercentage); + double correctDayPercentage = 100.0 * numCorrectDay / totalPodcasts; + System.out.println("Correct day: " + correctDayPercentage); + double offByLessThan2daysPercentage = 100.0 * (totalPodcasts - numOffByMoreThan2days) / totalPodcasts; + System.out.println("Off by less than 2 days: " + offByLessThan2daysPercentage); + + assertTrue(schedulePercentage > 80); + assertTrue(offByLessThan3HoursPercentage > 55); + assertTrue(scheduleAndCorrectDayPercentage > 75); + assertTrue(correctDayPercentage > 60); + assertTrue(offByLessThan2daysPercentage > 75); + + printHistogram(histogram); + } +}
\ No newline at end of file diff --git a/core/src/test/java/de/danoeh/antennapod/core/util/ReleaseScheduleGuesserTest.java b/core/src/test/java/de/danoeh/antennapod/core/util/ReleaseScheduleGuesserTest.java new file mode 100644 index 000000000..aaad125c9 --- /dev/null +++ b/core/src/test/java/de/danoeh/antennapod/core/util/ReleaseScheduleGuesserTest.java @@ -0,0 +1,172 @@ +package de.danoeh.antennapod.core.util; + +import org.junit.Test; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Locale; + +import static de.danoeh.antennapod.core.util.ReleaseScheduleGuesser.ONE_DAY; +import static de.danoeh.antennapod.core.util.ReleaseScheduleGuesser.ONE_HOUR; +import static de.danoeh.antennapod.core.util.ReleaseScheduleGuesser.ONE_MINUTE; +import static de.danoeh.antennapod.core.util.ReleaseScheduleGuesser.performGuess; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ReleaseScheduleGuesserTest { + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ROOT); + + private Date makeDate(String dateStr) { + try { + return DATE_FORMAT.parse(dateStr); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + private void assertClose(Date expected, Date actual, long tolerance) { + assertTrue("Date should differ at most " + tolerance / 60000 + " minutes from " + + DATE_FORMAT.format(expected) + ", but is " + DATE_FORMAT.format(actual), + Math.abs(expected.getTime() - actual.getTime()) < tolerance); + } + + @Test + public void testEdgeCases() { + ArrayList<Date> releaseDates = new ArrayList<>(); + assertEquals(ReleaseScheduleGuesser.Schedule.UNKNOWN, performGuess(releaseDates).schedule); + releaseDates.add(makeDate("2024-01-01 16:30")); + assertEquals(ReleaseScheduleGuesser.Schedule.UNKNOWN, performGuess(releaseDates).schedule); + } + + @Test + public void testDaily() { + ArrayList<Date> releaseDates = new ArrayList<>(); + releaseDates.add(makeDate("2024-01-01 16:30")); // Monday + releaseDates.add(makeDate("2024-01-02 16:25")); + releaseDates.add(makeDate("2024-01-03 16:35")); + releaseDates.add(makeDate("2024-01-04 16:40")); + releaseDates.add(makeDate("2024-01-05 16:20")); + releaseDates.add(makeDate("2024-01-06 16:10")); + releaseDates.add(makeDate("2024-01-07 16:32")); // Sunday + + // Next day + ReleaseScheduleGuesser.Guess guess = performGuess(releaseDates); + assertEquals(ReleaseScheduleGuesser.Schedule.DAILY, guess.schedule); + assertClose(makeDate("2024-01-08 16:30"), guess.nextExpectedDate, 10 * ONE_MINUTE); + + // One-off early release + releaseDates.add(makeDate("2024-01-08 10:00")); + guess = performGuess(releaseDates); + assertEquals(ReleaseScheduleGuesser.Schedule.DAILY, guess.schedule); + assertClose(makeDate("2024-01-09 16:30"), guess.nextExpectedDate, 10 * ONE_MINUTE); + } + + @Test + public void testWeekdays() { + ArrayList<Date> releaseDates = new ArrayList<>(); + releaseDates.add(makeDate("2024-01-01 16:30")); // Monday + releaseDates.add(makeDate("2024-01-02 16:25")); + releaseDates.add(makeDate("2024-01-03 16:35")); + releaseDates.add(makeDate("2024-01-04 16:40")); + releaseDates.add(makeDate("2024-01-05 16:20")); // Friday + releaseDates.add(makeDate("2024-01-08 16:20")); // Monday + releaseDates.add(makeDate("2024-01-09 16:30")); + releaseDates.add(makeDate("2024-01-10 16:40")); + releaseDates.add(makeDate("2024-01-11 16:45")); // Thursday + + // Next day + ReleaseScheduleGuesser.Guess guess = performGuess(releaseDates); + assertEquals(ReleaseScheduleGuesser.Schedule.WEEKDAYS, guess.schedule); + assertClose(makeDate("2024-01-12 16:30"), guess.nextExpectedDate, ONE_HOUR); + + // After weekend + releaseDates.add(makeDate("2024-01-12 16:30")); // Friday + guess = performGuess(releaseDates); + assertClose(makeDate("2024-01-15 16:30"), guess.nextExpectedDate, ONE_HOUR); + } + + @Test + public void testWeekly() { + ArrayList<Date> releaseDates = new ArrayList<>(); + releaseDates.add(makeDate("2024-01-07 16:30")); // Sunday + releaseDates.add(makeDate("2024-01-14 16:25")); + releaseDates.add(makeDate("2024-01-21 14:25")); + releaseDates.add(makeDate("2024-01-28 16:15")); + + // Next week + ReleaseScheduleGuesser.Guess guess = performGuess(releaseDates); + assertEquals(ReleaseScheduleGuesser.Schedule.WEEKLY, guess.schedule); + assertClose(makeDate("2024-02-04 16:30"), guess.nextExpectedDate, 2 * ONE_HOUR); + + // One-off early release + releaseDates.add(makeDate("2024-02-02 16:35")); + guess = performGuess(releaseDates); + assertEquals(ReleaseScheduleGuesser.Schedule.WEEKLY, guess.schedule); + assertClose(makeDate("2024-02-11 16:30"), guess.nextExpectedDate, 2 * ONE_HOUR); + + // One-off late release + releaseDates.add(makeDate("2024-02-13 16:35")); + guess = performGuess(releaseDates); + assertEquals(ReleaseScheduleGuesser.Schedule.WEEKLY, guess.schedule); + assertClose(makeDate("2024-02-18 16:30"), guess.nextExpectedDate, 2 * ONE_HOUR); + } + + @Test + public void testMonthly() { + ArrayList<Date> releaseDates = new ArrayList<>(); + releaseDates.add(makeDate("2024-01-01 16:30")); + releaseDates.add(makeDate("2024-02-01 16:30")); + releaseDates.add(makeDate("2024-03-01 16:30")); + releaseDates.add(makeDate("2024-04-01 16:30")); + + // Next month + ReleaseScheduleGuesser.Guess guess = performGuess(releaseDates); + assertEquals(ReleaseScheduleGuesser.Schedule.MONTHLY, guess.schedule); + assertClose(makeDate("2024-05-01 16:30"), guess.nextExpectedDate, 10 * ONE_HOUR); + + // One-off early release + releaseDates.add(makeDate("2024-04-30 16:30")); + guess = performGuess(releaseDates); + assertEquals(ReleaseScheduleGuesser.Schedule.MONTHLY, guess.schedule); + assertClose(makeDate("2024-06-01 16:30"), guess.nextExpectedDate, 10 * ONE_HOUR); + + // One-off late release + releaseDates.remove(releaseDates.size() - 1); + releaseDates.add(makeDate("2024-05-13 16:30")); + guess = performGuess(releaseDates); + assertEquals(ReleaseScheduleGuesser.Schedule.MONTHLY, guess.schedule); + assertClose(makeDate("2024-06-01 16:30"), guess.nextExpectedDate, 10 * ONE_HOUR); + } + + @Test + public void testFourweekly() { + ArrayList<Date> releaseDates = new ArrayList<>(); + releaseDates.add(makeDate("2024-01-01 16:30")); + releaseDates.add(makeDate("2024-01-29 16:30")); + releaseDates.add(makeDate("2024-02-26 16:30")); + releaseDates.add(makeDate("2024-03-25 16:30")); + + // 4 weeks later + ReleaseScheduleGuesser.Guess guess = performGuess(releaseDates); + assertEquals(ReleaseScheduleGuesser.Schedule.FOURWEEKLY, guess.schedule); + assertClose(makeDate("2024-04-22 16:30"), guess.nextExpectedDate, 10 * ONE_HOUR); + } + + @Test + public void testUnknown() { + ArrayList<Date> releaseDates = new ArrayList<>(); + releaseDates.add(makeDate("2024-01-01 16:30")); + releaseDates.add(makeDate("2024-01-03 16:30")); + releaseDates.add(makeDate("2024-01-03 16:31")); + releaseDates.add(makeDate("2024-01-04 16:30")); + releaseDates.add(makeDate("2024-01-04 16:31")); + releaseDates.add(makeDate("2024-01-07 16:30")); + releaseDates.add(makeDate("2024-01-07 16:31")); + releaseDates.add(makeDate("2024-01-10 16:30")); + ReleaseScheduleGuesser.Guess guess = performGuess(releaseDates); + assertEquals(ReleaseScheduleGuesser.Schedule.UNKNOWN, guess.schedule); + assertClose(makeDate("2024-01-12 16:30"), guess.nextExpectedDate, 2 * ONE_DAY); + } +}
\ No newline at end of file |