diff --git a/application/src/main/java/org/togetherjava/tjbot/features/rss/FailureState.java b/application/src/main/java/org/togetherjava/tjbot/features/rss/FailureState.java index a5f8d41f40..02f7c309d5 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/rss/FailureState.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/rss/FailureState.java @@ -1,6 +1,6 @@ package org.togetherjava.tjbot.features.rss; -import java.time.ZonedDateTime; +import java.time.Instant; -record FailureState(int count, ZonedDateTime lastFailure) { +record FailureState(int count, Instant lastFailure) { } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/rss/RSSHandlerRoutine.java b/application/src/main/java/org/togetherjava/tjbot/features/rss/RSSHandlerRoutine.java index 1d89896038..55f262269e 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/rss/RSSHandlerRoutine.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/rss/RSSHandlerRoutine.java @@ -28,11 +28,11 @@ import javax.annotation.Nonnull; import java.io.IOException; -import java.time.LocalDateTime; -import java.time.ZoneId; +import java.time.Instant; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoUnit; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -74,8 +74,6 @@ public final class RSSHandlerRoutine implements Routine { private static final Logger logger = LoggerFactory.getLogger(RSSHandlerRoutine.class); private static final int MAX_CONTENTS = 1000; - private static final ZonedDateTime ZONED_TIME_MIN = - ZonedDateTime.of(LocalDateTime.MIN, ZoneId.systemDefault()); private static final String HTTP_USER_AGENT = "TJ-Bot/1.0 (+https://github.com/Together-Java/TJ-Bot)"; private final RssReader rssReader; @@ -181,7 +179,7 @@ private void sendRSS(JDA jda, RSSFeed feedConfig) { private Optional> prepareItemPostPredicate(RSSFeed feedConfig, List rssItems) { Optional rssFeedRecord = getRssFeedRecordFromDatabase(feedConfig); - Optional lastPostedDate = + Optional lastPostedDate = getLatestPostDateFromItems(rssItems, feedConfig.dateFormatterPattern()); lastPostedDate.ifPresent( @@ -191,17 +189,24 @@ private Optional> prepareItemPostPredicate(RSSFeed feedConfig, return Optional.empty(); } - Optional lastSavedDate = getLastSavedDateFromDatabaseRecord( - rssFeedRecord.orElseThrow(), feedConfig.dateFormatterPattern()); + Instant lastSavedDate; + try { + lastSavedDate = getLastSavedDateFromDatabaseRecord(rssFeedRecord.orElseThrow()); + } catch (DateTimeParseException _) { + Optional convertedDate = convertDateTimeToInstant(feedConfig); - if (lastSavedDate.isEmpty()) { - return Optional.empty(); + if (convertedDate.isEmpty()) { + return Optional.empty(); + } + + lastSavedDate = convertedDate.get(); } + final Instant convertedLastSavedDate = lastSavedDate; + return Optional.of(item -> { - ZonedDateTime itemPubDate = - getDateTimeFromItem(item, feedConfig.dateFormatterPattern()); - return itemPubDate.isAfter(lastSavedDate.orElseThrow()); + Instant itemPubDate = getDateTimeFromItem(item, feedConfig.dateFormatterPattern()); + return itemPubDate.isAfter(convertedLastSavedDate); }); } @@ -223,19 +228,12 @@ private Optional getRssFeedRecordFromDatabase(RSSFeed feedConfig) * record. * * @param rssRecord an existing RSS feed record to retrieve the last saved date from - * @param dateFormatterPattern the pattern used to parse the date from the database record * @return An {@link Optional} containing the last saved date if it could be retrieved and * parsed successfully, otherwise an empty {@link Optional} */ - private Optional getLastSavedDateFromDatabaseRecord(RssFeedRecord rssRecord, - String dateFormatterPattern) throws DateTimeParseException { - try { - ZonedDateTime savedDate = - getZonedDateTime(rssRecord.getLastDate(), dateFormatterPattern); - return Optional.of(savedDate); - } catch (DateTimeParseException _) { - return Optional.empty(); - } + private Instant getLastSavedDateFromDatabaseRecord(RssFeedRecord rssRecord) + throws DateTimeParseException { + return Instant.parse(rssRecord.getLastDate()); } /** @@ -243,13 +241,13 @@ private Optional getLastSavedDateFromDatabaseRecord(RssFeedRecord * * @param items the list of items to retrieve the latest post date from * @param dateFormatterPattern the pattern used to parse the date from the database record - * @return the latest post date as a {@link ZonedDateTime} object, or null if the list is empty + * @return the latest post date as a {@link Instant} object, or null if the list is empty */ - private Optional getLatestPostDateFromItems(List items, + private Optional getLatestPostDateFromItems(List items, String dateFormatterPattern) { return items.stream() .map(item -> getDateTimeFromItem(item, dateFormatterPattern)) - .max(ZonedDateTime::compareTo); + .max(Instant::compareTo); } /** @@ -277,10 +275,8 @@ private void postItem(List textChannels, Item rssItem, RSSFeed feed * @throws DateTimeParseException if the date cannot be parsed */ private void updateLastDateToDatabase(RSSFeed feedConfig, @Nullable RssFeedRecord rssFeedRecord, - ZonedDateTime lastPostedDate) { - DateTimeFormatter dateTimeFormatter = - DateTimeFormatter.ofPattern(feedConfig.dateFormatterPattern()); - String lastDateStr = lastPostedDate.format(dateTimeFormatter); + Instant lastPostedDate) { + String lastDateStr = lastPostedDate.toString(); if (rssFeedRecord == null) { database.write(context -> context.newRecord(RSS_FEED) @@ -297,22 +293,22 @@ private void updateLastDateToDatabase(RSSFeed feedConfig, @Nullable RssFeedRecor } /** - * Attempts to get a {@link ZonedDateTime} from an {@link Item} with a provided string date time + * Attempts to get a {@link Instant} from an {@link Item} with a provided string date time * format. *

* If either of the function inputs are null or a {@link DateTimeParseException} is caught, the - * oldest-possible {@link ZonedDateTime} will get returned instead. + * oldest-possible {@link Instant} will get returned instead. * * @param item The {@link Item} from which to extract the date. * @param dateTimeFormat The format of the date time string. - * @return The computed {@link ZonedDateTime} + * @return The computed {@link Instant} * @throws DateTimeParseException if the date cannot be parsed */ - private static ZonedDateTime getDateTimeFromItem(Item item, String dateTimeFormat) + private static Instant getDateTimeFromItem(Item item, String dateTimeFormat) throws DateTimeParseException { Optional pubDate = item.getPubDate(); - return pubDate.map(s -> getZonedDateTime(s, dateTimeFormat)).orElse(ZONED_TIME_MIN); + return pubDate.map(s -> parseDateTime(s, dateTimeFormat)).orElse(Instant.MIN); } @@ -334,13 +330,38 @@ private static boolean isValidDateFormat(Item rssItem, RSSFeed feedConfig) { // If this throws a DateTimeParseException then it's certain // that the format pattern defined in the config and the // feed's actual format differ. - getZonedDateTime(firstRssFeedPubDate.get(), feedConfig.dateFormatterPattern()); + parseDateTime(firstRssFeedPubDate.get(), feedConfig.dateFormatterPattern()); } catch (DateTimeParseException _) { return false; } return true; } + private Optional convertDateTimeToInstant(RSSFeed feedConfig) { + Optional feedOptional = getRssFeedRecordFromDatabase(feedConfig); + String dateTimeFormat = feedConfig.dateFormatterPattern(); + + if (feedOptional.isEmpty() || dateTimeFormat.isEmpty()) { + return Optional.empty(); + } + + RssFeedRecord feedRecord = feedOptional.get(); + String lastDate = feedRecord.getLastDate(); + + ZonedDateTime zonedDateTime; + try { + zonedDateTime = + ZonedDateTime.parse(lastDate, DateTimeFormatter.ofPattern(dateTimeFormat)); + } catch (DateTimeParseException exception) { + logger.error( + "Attempted to convert date time from database ({}) to instant, but failed:", + lastDate, exception); + return Optional.empty(); + } + + return Optional.of(zonedDateTime.toInstant()); + } + /** * Attempts to find text channels from a given RSS feed configuration. * @@ -383,8 +404,8 @@ private MessageCreateData constructMessage(Item item, RSSFeed feedConfig) { // Set the item's timestamp to the embed if found item.getPubDate() - .ifPresent(date -> embedBuilder - .setTimestamp(getZonedDateTime(date, feedConfig.dateFormatterPattern()))); + .ifPresent(dateTime -> embedBuilder + .setTimestamp(parseDateTime(dateTime, feedConfig.dateFormatterPattern()))); embedBuilder.setTitle(title, titleLink); embedBuilder.setAuthor(item.getChannel().getLink()); @@ -428,7 +449,7 @@ private List fetchRSSItemsFromURL(String rssUrl) { "Possibly dead RSS feed URL: {} - Failed {} times. Please remove it from config.", rssUrl, newCount); } - circuitBreaker.put(rssUrl, new FailureState(newCount, ZonedDateTime.now())); + circuitBreaker.put(rssUrl, new FailureState(newCount, Instant.now())); long blacklistedHours = calculateWaitHours(newCount); @@ -441,21 +462,19 @@ private List fetchRSSItemsFromURL(String rssUrl) { } /** - * Helper function for parsing a given date value to a {@link ZonedDateTime} with a given - * format. + * Helper function for parsing a given date value to a {@link Instant} with a given format. * - * @param date the date value to parse, can be null - * @param format the format pattern to use for parsing - * @return the parsed {@link ZonedDateTime} object + * @param dateTime the date and time value to parse, can be null + * @return the parsed {@link Instant} object * @throws DateTimeParseException if the date cannot be parsed */ - private static ZonedDateTime getZonedDateTime(@Nullable String date, String format) + private static Instant parseDateTime(@Nullable String dateTime, String datePattern) throws DateTimeParseException { - if (date == null) { - return ZONED_TIME_MIN; + if (dateTime == null) { + return Instant.MIN; } - return ZonedDateTime.parse(date, DateTimeFormatter.ofPattern(format)); + return Instant.from(DateTimeFormatter.ofPattern(datePattern).parse(dateTime)); } private long calculateWaitHours(int failureCount) { @@ -470,8 +489,8 @@ private boolean isBackingOff(String url) { } long waitHours = calculateWaitHours(state.count()); - ZonedDateTime retryAt = state.lastFailure().plusHours(waitHours); + Instant retryAt = state.lastFailure().plus(waitHours, ChronoUnit.HOURS); - return ZonedDateTime.now().isBefore(retryAt); + return Instant.now().isBefore(retryAt); } }