diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/localization/DateWrapper.java b/extractor/src/main/java/org/schabi/newpipe/extractor/localization/DateWrapper.java index 2f0a2bdd2a..00dcd124df 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/localization/DateWrapper.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/localization/DateWrapper.java @@ -1,12 +1,16 @@ package org.schabi.newpipe.extractor.localization; +import org.schabi.newpipe.extractor.exceptions.ParsingException; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.Serializable; +import java.time.Instant; +import java.time.LocalDateTime; import java.time.OffsetDateTime; +import java.time.ZoneId; import java.time.ZoneOffset; -import java.util.Calendar; -import java.util.GregorianCalendar; +import java.time.format.DateTimeParseException; /** * A wrapper class that provides a field to describe if the date/time is precise or just an @@ -14,52 +18,61 @@ */ public class DateWrapper implements Serializable { @Nonnull - private final OffsetDateTime offsetDateTime; + private final Instant instant; private final boolean isApproximation; - /** - * @deprecated Use {@link #DateWrapper(OffsetDateTime)} instead. - */ - @Deprecated - public DateWrapper(@Nonnull final Calendar calendar) { - //noinspection deprecation - this(calendar, false); - } - - /** - * @deprecated Use {@link #DateWrapper(OffsetDateTime, boolean)} instead. - */ - @Deprecated - public DateWrapper(@Nonnull final Calendar calendar, final boolean isApproximation) { - this(OffsetDateTime.ofInstant(calendar.toInstant(), ZoneOffset.UTC), isApproximation); - } - public DateWrapper(@Nonnull final OffsetDateTime offsetDateTime) { this(offsetDateTime, false); } public DateWrapper(@Nonnull final OffsetDateTime offsetDateTime, final boolean isApproximation) { - this.offsetDateTime = offsetDateTime.withOffsetSameInstant(ZoneOffset.UTC); + this(offsetDateTime.toInstant(), isApproximation); + } + + public DateWrapper(@Nonnull final Instant instant) { + this(instant, false); + } + + public DateWrapper(@Nonnull final Instant instant, final boolean isApproximation) { + this.instant = instant; this.isApproximation = isApproximation; } + public DateWrapper(@Nonnull final LocalDateTime dateTime, final boolean isApproximation) { + this(dateTime.atZone(ZoneId.systemDefault()).toInstant(), isApproximation); + } + /** - * @return the wrapped date/time as a {@link Calendar}. - * @deprecated use {@link #offsetDateTime()} instead. + * @return the wrapped {@link Instant} */ - @Deprecated @Nonnull - public Calendar date() { - return GregorianCalendar.from(offsetDateTime.toZonedDateTime()); + public Instant getInstant() { + return instant; } /** - * @return the wrapped date/time. + * @return the wrapped {@link Instant} as an {@link OffsetDateTime} set to UTC. */ @Nonnull public OffsetDateTime offsetDateTime() { - return offsetDateTime; + return instant.atOffset(ZoneOffset.UTC); + } + + /** + * @return the wrapped {@link Instant} as a {@link LocalDateTime} in the current time zone. + */ + @Nonnull + public LocalDateTime getLocalDateTime() { + return getLocalDateTime(ZoneId.systemDefault()); + } + + /** + * @return the wrapped {@link Instant} as a {@link LocalDateTime} in the given time zone. + */ + @Nonnull + public LocalDateTime getLocalDateTime(@Nonnull final ZoneId zoneId) { + return LocalDateTime.ofInstant(instant, zoneId); } /** @@ -73,8 +86,42 @@ public boolean isApproximation() { @Override public String toString() { return "DateWrapper{" - + "offsetDateTime=" + offsetDateTime + + "instant=" + instant + ", isApproximation=" + isApproximation + '}'; } + + /** + * Parses a date string that matches the ISO-8601 {@link OffsetDateTime} pattern, e.g. + * "2011-12-03T10:15:30+01:00". + * + * @param date The date string + * @return a non-approximate {@link DateWrapper}, or null if the string is null + * @throws ParsingException if the string does not match the expected format + */ + @Nullable + public static DateWrapper fromOffsetDateTime(final String date) throws ParsingException { + try { + return date != null ? new DateWrapper(OffsetDateTime.parse(date)) : null; + } catch (final DateTimeParseException e) { + throw new ParsingException("Could not parse date: \"" + date + "\"", e); + } + } + + /** + * Parses a date string that matches the ISO-8601 {@link Instant} pattern, e.g. + * "2011-12-03T10:15:30Z". + * + * @param date The date string + * @return a non-approximate {@link DateWrapper}, or null if the string is null + * @throws ParsingException if the string does not match the expected format + */ + @Nullable + public static DateWrapper fromInstant(final String date) throws ParsingException { + try { + return date != null ? new DateWrapper(Instant.parse(date)) : null; + } catch (final DateTimeParseException e) { + throw new ParsingException("Could not parse date: \"" + date + "\"", e); + } + } } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/localization/TimeAgoParser.java b/extractor/src/main/java/org/schabi/newpipe/extractor/localization/TimeAgoParser.java index 619de9e74b..88e04b803c 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/localization/TimeAgoParser.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/localization/TimeAgoParser.java @@ -4,8 +4,7 @@ import org.schabi.newpipe.extractor.timeago.PatternsHolder; import org.schabi.newpipe.extractor.utils.Parser; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; +import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.Map; import java.util.regex.Pattern; @@ -16,20 +15,7 @@ */ public class TimeAgoParser { private final PatternsHolder patternsHolder; - private final OffsetDateTime now; - - /** - * Creates a helper to parse upload dates in the format '2 days ago'. - *

- * Instantiate a new {@link TimeAgoParser} every time you extract a new batch of items. - *

- * - * @param patternsHolder An object that holds the "time ago" patterns, special cases, and the - * language word separator. - */ - public TimeAgoParser(final PatternsHolder patternsHolder) { - this(patternsHolder, OffsetDateTime.now(ZoneOffset.UTC)); - } + private final LocalDateTime now; /** * Creates a helper to parse upload dates in the format '2 days ago'. @@ -41,7 +27,7 @@ public TimeAgoParser(final PatternsHolder patternsHolder) { * language word separator. * @param now The current time */ - public TimeAgoParser(final PatternsHolder patternsHolder, final OffsetDateTime now) { + public TimeAgoParser(final PatternsHolder patternsHolder, final LocalDateTime now) { this.patternsHolder = patternsHolder; this.now = now; } @@ -118,34 +104,13 @@ private boolean textualDateMatches(final String textualDate, final String agoPhr } private DateWrapper getResultFor(final int timeAgoAmount, final ChronoUnit chronoUnit) { - OffsetDateTime offsetDateTime = now; - boolean isApproximation = false; - - switch (chronoUnit) { - case SECONDS: - case MINUTES: - case HOURS: - offsetDateTime = offsetDateTime.minus(timeAgoAmount, chronoUnit); - break; - - case DAYS: - case WEEKS: - case MONTHS: - offsetDateTime = offsetDateTime.minus(timeAgoAmount, chronoUnit); - isApproximation = true; - break; - - case YEARS: + final var localDateTime = chronoUnit == ChronoUnit.YEARS // minusDays is needed to prevent `PrettyTime` from showing '12 months ago'. - offsetDateTime = offsetDateTime.minusYears(timeAgoAmount).minusDays(1); - isApproximation = true; - break; - } - - if (isApproximation) { - offsetDateTime = offsetDateTime.truncatedTo(ChronoUnit.HOURS); - } - - return new DateWrapper(offsetDateTime, isApproximation); + ? now.minusYears(timeAgoAmount).minusDays(1) + : now.minus(timeAgoAmount, chronoUnit); + final boolean isApproximate = chronoUnit.isDateBased(); + final var resolvedDateTime = + isApproximate ? localDateTime.truncatedTo(ChronoUnit.DAYS) : localDateTime; + return new DateWrapper(resolvedDateTime, isApproximate); } } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/localization/TimeAgoPatternsManager.java b/extractor/src/main/java/org/schabi/newpipe/extractor/localization/TimeAgoPatternsManager.java index 47889a5d32..67cfb93a64 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/localization/TimeAgoPatternsManager.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/localization/TimeAgoPatternsManager.java @@ -3,7 +3,7 @@ import org.schabi.newpipe.extractor.timeago.PatternsHolder; import org.schabi.newpipe.extractor.timeago.PatternsManager; -import java.time.OffsetDateTime; +import java.time.LocalDateTime; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -20,25 +20,13 @@ private static PatternsHolder getPatternsFor(@Nonnull final Localization localiz @Nullable public static TimeAgoParser getTimeAgoParserFor(@Nonnull final Localization localization) { - final PatternsHolder holder = getPatternsFor(localization); - - if (holder == null) { - return null; - } - - return new TimeAgoParser(holder); + return getTimeAgoParserFor(localization, LocalDateTime.now()); } @Nullable - public static TimeAgoParser getTimeAgoParserFor( - @Nonnull final Localization localization, - @Nonnull final OffsetDateTime now) { + public static TimeAgoParser getTimeAgoParserFor(@Nonnull final Localization localization, + @Nonnull final LocalDateTime now) { final PatternsHolder holder = getPatternsFor(localization); - - if (holder == null) { - return null; - } - - return new TimeAgoParser(holder, now); + return holder == null ? null : new TimeAgoParser(holder, now); } } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampExtractorHelper.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampExtractorHelper.java index b230079109..99f026d7ed 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampExtractorHelper.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampExtractorHelper.java @@ -204,7 +204,7 @@ public static DateWrapper parseDate(final String textDate) throws ParsingExcepti try { final ZonedDateTime zonedDateTime = ZonedDateTime.parse(textDate, DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm:ss zzz", Locale.ENGLISH)); - return new DateWrapper(zonedDateTime.toOffsetDateTime(), false); + return new DateWrapper(zonedDateTime.toInstant()); } catch (final DateTimeException e) { throw new ParsingException("Could not parse date '" + textDate + "'", e); } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCParsingHelper.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCParsingHelper.java index 2a4ea6a9f8..89ef3943b0 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCParsingHelper.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCParsingHelper.java @@ -8,15 +8,12 @@ import org.schabi.newpipe.extractor.Image.ResolutionLevel; import org.schabi.newpipe.extractor.downloader.Downloader; import org.schabi.newpipe.extractor.exceptions.ExtractionException; -import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; import org.schabi.newpipe.extractor.localization.Localization; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.IOException; -import java.time.OffsetDateTime; -import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -33,15 +30,6 @@ public final class MediaCCCParsingHelper { private MediaCCCParsingHelper() { } - public static OffsetDateTime parseDateFrom(final String textualUploadDate) - throws ParsingException { - try { - return OffsetDateTime.parse(textualUploadDate); - } catch (final DateTimeParseException e) { - throw new ParsingException("Could not parse date: \"" + textualUploadDate + "\"", e); - } - } - /** * Check whether an id is a live stream id * @param id the {@code id} to check diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCRecentKiosk.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCRecentKiosk.java index d38d65fd53..6223304597 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCRecentKiosk.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCRecentKiosk.java @@ -52,12 +52,10 @@ public InfoItemsPage getInitialPage() throws IOException, Extrac // Streams in the recent kiosk are not ordered by the release date. // Sort them to have the latest stream at the beginning of the list. - final Comparator comparator = Comparator - .comparing(StreamInfoItem::getUploadDate, Comparator - .nullsLast(Comparator.comparing(DateWrapper::offsetDateTime))) + final var comparator = Comparator.comparing(StreamInfoItem::getUploadDate, + Comparator.nullsLast(Comparator.comparing(DateWrapper::getInstant))) .reversed(); - final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId(), - comparator); + final var collector = new StreamInfoItemsCollector(getServiceId(), comparator); events.stream() .filter(JsonObject.class::isInstance) diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCRecentKioskExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCRecentKioskExtractor.java index df25b28d89..a8d5ae3c36 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCRecentKioskExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCRecentKioskExtractor.java @@ -92,6 +92,6 @@ public String getTextualUploadDate() throws ParsingException { public DateWrapper getUploadDate() throws ParsingException { final ZonedDateTime zonedDateTime = ZonedDateTime.parse(event.getString("date"), DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSzzzz")); - return new DateWrapper(zonedDateTime.toOffsetDateTime(), false); + return new DateWrapper(zonedDateTime.toInstant()); } } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java index 99ddf5e08c..0cb0ac4de8 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java @@ -2,7 +2,6 @@ import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getImageListFromLogoImageUrl; import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getThumbnailsFromStreamItem; -import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.parseDateFrom; import static org.schabi.newpipe.extractor.stream.AudioStream.UNKNOWN_BITRATE; import static org.schabi.newpipe.extractor.stream.Stream.ID_UNKNOWN; @@ -37,6 +36,7 @@ import java.util.Locale; import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class MediaCCCStreamExtractor extends StreamExtractor { private JsonObject data; @@ -46,16 +46,16 @@ public MediaCCCStreamExtractor(final StreamingService service, final LinkHandler super(service, linkHandler); } - @Nonnull + @Nullable @Override public String getTextualUploadDate() { return data.getString("release_date"); } - @Nonnull + @Nullable @Override public DateWrapper getUploadDate() throws ParsingException { - return new DateWrapper(parseDateFrom(getTextualUploadDate())); + return DateWrapper.fromOffsetDateTime(getTextualUploadDate()); } @Nonnull diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/infoItems/MediaCCCStreamInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/infoItems/MediaCCCStreamInfoItemExtractor.java index ec9d00f3a0..e165d6c438 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/infoItems/MediaCCCStreamInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/infoItems/MediaCCCStreamInfoItemExtractor.java @@ -4,7 +4,6 @@ import org.schabi.newpipe.extractor.Image; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.localization.DateWrapper; -import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper; import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor; import org.schabi.newpipe.extractor.stream.StreamType; @@ -65,11 +64,8 @@ public String getTextualUploadDate() { @Nullable @Override public DateWrapper getUploadDate() throws ParsingException { - final String date = getTextualUploadDate(); - if (date == null) { - return null; // event is in the future... - } - return new DateWrapper(MediaCCCParsingHelper.parseDateFrom(date)); + // if null, event is in the future... + return DateWrapper.fromOffsetDateTime(getTextualUploadDate()); } @Override diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/PeertubeParsingHelper.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/PeertubeParsingHelper.java index 4e8bd2d350..3d87ad88cb 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/PeertubeParsingHelper.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/PeertubeParsingHelper.java @@ -18,10 +18,6 @@ import org.schabi.newpipe.extractor.utils.Parser; import javax.annotation.Nonnull; -import java.time.Instant; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -48,15 +44,6 @@ public static void validate(final JsonObject json) throws ContentNotAvailableExc } } - public static OffsetDateTime parseDateFrom(final String textualUploadDate) - throws ParsingException { - try { - return OffsetDateTime.ofInstant(Instant.parse(textualUploadDate), ZoneOffset.UTC); - } catch (final DateTimeParseException e) { - throw new ParsingException("Could not parse date: \"" + textualUploadDate + "\"", e); - } - } - public static Page getNextPage(final String prevPageUrl, final long total) { final String prevStart; try { diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/extractors/PeertubeCommentsInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/extractors/PeertubeCommentsInfoItemExtractor.java index 57a0444906..80c8bbbc70 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/extractors/PeertubeCommentsInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/extractors/PeertubeCommentsInfoItemExtractor.java @@ -23,7 +23,6 @@ import static org.schabi.newpipe.extractor.services.peertube.extractors.PeertubeCommentsExtractor.CHILDREN; import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getAvatarsFromOwnerAccountOrVideoChannelObject; -import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.parseDateFrom; public class PeertubeCommentsInfoItemExtractor implements CommentsInfoItemExtractor { @Nonnull @@ -73,8 +72,7 @@ public String getTextualUploadDate() throws ParsingException { @Override public DateWrapper getUploadDate() throws ParsingException { - final String textualUploadDate = getTextualUploadDate(); - return new DateWrapper(parseDateFrom(textualUploadDate)); + return DateWrapper.fromInstant(getTextualUploadDate()); } @Nonnull diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/extractors/PeertubeStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/extractors/PeertubeStreamExtractor.java index 5406368854..bf0e02b593 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/extractors/PeertubeStreamExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/extractors/PeertubeStreamExtractor.java @@ -79,13 +79,7 @@ public String getTextualUploadDate() throws ParsingException { @Override public DateWrapper getUploadDate() throws ParsingException { - final String textualUploadDate = getTextualUploadDate(); - - if (textualUploadDate == null) { - return null; - } - - return new DateWrapper(PeertubeParsingHelper.parseDateFrom(textualUploadDate)); + return DateWrapper.fromInstant(getTextualUploadDate()); } @Nonnull diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/extractors/PeertubeStreamInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/extractors/PeertubeStreamInfoItemExtractor.java index 46aae43ccf..7016fe047f 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/extractors/PeertubeStreamInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/extractors/PeertubeStreamInfoItemExtractor.java @@ -14,7 +14,6 @@ import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getAvatarsFromOwnerAccountOrVideoChannelObject; import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getThumbnailsFromPlaylistOrVideoItem; -import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.parseDateFrom; public class PeertubeStreamInfoItemExtractor implements StreamInfoItemExtractor { @@ -85,13 +84,7 @@ public String getTextualUploadDate() throws ParsingException { @Override public DateWrapper getUploadDate() throws ParsingException { - final String textualUploadDate = getTextualUploadDate(); - - if (textualUploadDate == null) { - return null; - } - - return new DateWrapper(parseDateFrom(textualUploadDate)); + return DateWrapper.fromInstant(getTextualUploadDate()); } @Override diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java index ae8fd77d6d..aeff6bd363 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java @@ -23,6 +23,7 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; +import org.schabi.newpipe.extractor.localization.DateWrapper; import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudChannelInfoItemExtractor; import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudPlaylistInfoItemExtractor; import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudLikesInfoItemExtractor; @@ -133,17 +134,17 @@ public static synchronized String clientId() throws ExtractionException, IOExcep throw new ExtractionException("Couldn't extract client id"); } - public static OffsetDateTime parseDateFrom(final String textualUploadDate) - throws ParsingException { + @Nullable + public static DateWrapper parseDate(final String uploadDate) throws ParsingException { try { - return OffsetDateTime.parse(textualUploadDate); - } catch (final DateTimeParseException e1) { + return DateWrapper.fromInstant(uploadDate); + } catch (final DateTimeParseException e) { try { - return OffsetDateTime.parse(textualUploadDate, DateTimeFormatter - .ofPattern("yyyy/MM/dd HH:mm:ss +0000")); - } catch (final DateTimeParseException e2) { - throw new ParsingException("Could not parse date: \"" + textualUploadDate + "\"" - + ", " + e1.getMessage(), e2); + return new DateWrapper(OffsetDateTime.parse(uploadDate, + DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss +0000"))); + } catch (final DateTimeParseException e1) { + e1.addSuppressed(e); + throw new ParsingException("Could not parse date: \"" + uploadDate + "\"", e1); } } } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudCommentsInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudCommentsInfoItemExtractor.java index 5ff6d29db6..d5446e56bb 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudCommentsInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudCommentsInfoItemExtractor.java @@ -13,7 +13,7 @@ import java.util.Objects; import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromArtworkOrAvatarUrl; -import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.parseDateFrom; +import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.parseDate; public class SoundcloudCommentsInfoItemExtractor implements CommentsInfoItemExtractor { private final JsonObject json; @@ -69,7 +69,7 @@ public String getTextualUploadDate() { @Nullable @Override public DateWrapper getUploadDate() throws ParsingException { - return new DateWrapper(parseDateFrom(getTextualUploadDate())); + return parseDate(getTextualUploadDate()); } @Override diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudStreamExtractor.java index 595862bde7..4de7114de6 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudStreamExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudStreamExtractor.java @@ -5,7 +5,7 @@ import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromArtworkOrAvatarUrl; import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromTrackObject; import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAvatarUrl; -import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.parseDateFrom; +import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.parseDate; import static org.schabi.newpipe.extractor.stream.Stream.ID_UNKNOWN; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; @@ -88,18 +88,16 @@ public String getName() { return track.getString("title"); } - @Nonnull + @Nullable @Override public String getTextualUploadDate() { - return track.getString("created_at") - .replace("T", " ") - .replace("Z", ""); + return track.getString("created_at"); } - @Nonnull + @Nullable @Override public DateWrapper getUploadDate() throws ParsingException { - return new DateWrapper(parseDateFrom(track.getString("created_at"))); + return parseDate(getTextualUploadDate()); } @Nonnull diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudStreamInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudStreamInfoItemExtractor.java index 6fd6232e97..f12301738c 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudStreamInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudStreamInfoItemExtractor.java @@ -13,7 +13,7 @@ import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromArtworkOrAvatarUrl; import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromTrackObject; -import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.parseDateFrom; +import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.parseDate; import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps; public class SoundcloudStreamInfoItemExtractor implements StreamInfoItemExtractor { @@ -68,7 +68,7 @@ public String getTextualUploadDate() { @Override public DateWrapper getUploadDate() throws ParsingException { - return new DateWrapper(parseDateFrom(getTextualUploadDate())); + return parseDate(getTextualUploadDate()); } @Override diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java index 70aac8d2b5..df295f0d5c 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java @@ -67,10 +67,6 @@ import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; -import java.time.LocalDate; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeParseException; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -294,20 +290,6 @@ public static String getFeedUrlFrom(@Nonnull final String channelIdOrUser) { } } - public static OffsetDateTime parseDateFrom(final String textualUploadDate) - throws ParsingException { - try { - return OffsetDateTime.parse(textualUploadDate); - } catch (final DateTimeParseException e) { - try { - return LocalDate.parse(textualUploadDate).atStartOfDay().atOffset(ZoneOffset.UTC); - } catch (final DateTimeParseException e1) { - throw new ParsingException("Could not parse date: \"" + textualUploadDate + "\"", - e1); - } - } - } - /** * Checks if the given playlist id is a YouTube Mix (auto-generated playlist) * Ids from a YouTube Mix start with "RD" diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeFeedInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeFeedInfoItemExtractor.java index d917eb2d7b..748c96ecad 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeFeedInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeFeedInfoItemExtractor.java @@ -10,8 +10,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.time.OffsetDateTime; -import java.time.format.DateTimeParseException; import java.util.List; public class YoutubeFeedInfoItemExtractor implements StreamInfoItemExtractor { @@ -69,12 +67,7 @@ public String getTextualUploadDate() { @Nullable @Override public DateWrapper getUploadDate() throws ParsingException { - try { - return new DateWrapper(OffsetDateTime.parse(getTextualUploadDate())); - } catch (final DateTimeParseException e) { - throw new ParsingException("Could not parse date (\"" + getTextualUploadDate() + "\")", - e); - } + return DateWrapper.fromOffsetDateTime(getTextualUploadDate()); } @Override diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java index eb050fd13e..e6fa782ce7 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java @@ -87,20 +87,23 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.time.LocalDate; -import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.Optional; import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; public class YoutubeStreamExtractor extends StreamExtractor { + private static final String PREMIERED = "Premiered "; + private static final String PREMIERED_ON = "Premiered on "; @Nullable private static PoTokenProvider poTokenProvider; @@ -168,79 +171,74 @@ public String getName() throws ParsingException { @Nullable @Override - public String getTextualUploadDate() throws ParsingException { - if (!playerMicroFormatRenderer.getString("uploadDate", "").isEmpty()) { - return playerMicroFormatRenderer.getString("uploadDate"); - } else if (!playerMicroFormatRenderer.getString("publishDate", "").isEmpty()) { - return playerMicroFormatRenderer.getString("publishDate"); + public String getTextualUploadDate() { + String timestamp = playerMicroFormatRenderer.getString("uploadDate", ""); + if (timestamp.isEmpty()) { + timestamp = playerMicroFormatRenderer.getString("publishDate", ""); + } + if (!timestamp.isEmpty()) { + return timestamp; } - final JsonObject liveDetails = playerMicroFormatRenderer.getObject( - "liveBroadcastDetails"); - if (!liveDetails.getString("endTimestamp", "").isEmpty()) { - // an ended live stream - return liveDetails.getString("endTimestamp"); - } else if (!liveDetails.getString("startTimestamp", "").isEmpty()) { + final var liveDetails = playerMicroFormatRenderer.getObject("liveBroadcastDetails"); + timestamp = liveDetails.getString("endTimestamp", ""); // an ended live stream + if (timestamp.isEmpty()) { // a running live stream - return liveDetails.getString("startTimestamp"); + timestamp = liveDetails.getString("startTimestamp", ""); + } + if (!timestamp.isEmpty()) { + return timestamp; } else if (getStreamType() == StreamType.LIVE_STREAM) { // this should never be reached, but a live stream without upload date is valid return null; } - final String videoPrimaryInfoRendererDateText = - getTextFromObject(getVideoPrimaryInfoRenderer().getObject("dateText")); - - if (videoPrimaryInfoRendererDateText != null) { - if (videoPrimaryInfoRendererDateText.startsWith("Premiered")) { - final String time = videoPrimaryInfoRendererDateText.substring(13); - - try { // Premiered 20 hours ago - final TimeAgoParser timeAgoParser = TimeAgoPatternsManager.getTimeAgoParserFor( - new Localization("en")); - final OffsetDateTime parsedTime = timeAgoParser.parse(time).offsetDateTime(); - return DateTimeFormatter.ISO_LOCAL_DATE.format(parsedTime); - } catch (final Exception ignored) { - } - - try { // Premiered Feb 21, 2020 - final LocalDate localDate = LocalDate.parse(time, - DateTimeFormatter.ofPattern("MMM dd, yyyy", Locale.ENGLISH)); - return DateTimeFormatter.ISO_LOCAL_DATE.format(localDate); - } catch (final Exception ignored) { - } - - try { // Premiered on 21 Feb 2020 - final LocalDate localDate = LocalDate.parse(time, - DateTimeFormatter.ofPattern("dd MMM yyyy", Locale.ENGLISH)); - return DateTimeFormatter.ISO_LOCAL_DATE.format(localDate); - } catch (final Exception ignored) { - } - } - - try { - // TODO: this parses English formatted dates only, we need a better approach to - // parse the textual date - final LocalDate localDate = LocalDate.parse(videoPrimaryInfoRendererDateText, - DateTimeFormatter.ofPattern("dd MMM yyyy", Locale.ENGLISH)); - return DateTimeFormatter.ISO_LOCAL_DATE.format(localDate); - } catch (final Exception e) { - throw new ParsingException("Could not get upload date", e); - } + final var textObject = getVideoPrimaryInfoRenderer().getObject("dateText"); + final String rendererDateText = getTextFromObject(textObject); + if (rendererDateText == null) { + return null; + } else if (rendererDateText.startsWith(PREMIERED_ON)) { // Premiered on 21 Feb 2020 + return rendererDateText.substring(PREMIERED_ON.length()); + } else if (rendererDateText.startsWith(PREMIERED)) { + // Premiered 20 hours ago / Premiered Feb 21, 2020 + return rendererDateText.substring(PREMIERED.length()); + } else { + return rendererDateText; } - - throw new ParsingException("Could not get upload date"); } @Override public DateWrapper getUploadDate() throws ParsingException { - final String textualUploadDate = getTextualUploadDate(); + final String dateText = getTextualUploadDate(); + try { + return DateWrapper.fromOffsetDateTime(dateText); + } catch (final ParsingException e) { + // Try other patterns first + } - if (isNullOrEmpty(textualUploadDate)) { - return null; + try { // Premiered 20 hours ago + final var localization = new Localization("en"); + return TimeAgoPatternsManager.getTimeAgoParserFor(localization).parse(dateText); + } catch (final ParsingException e) { + // Try other patterns first } - return new DateWrapper(YoutubeParsingHelper.parseDateFrom(textualUploadDate), true); + return parseOptionalDate(dateText, "MMM dd, yyyy") + .or(() -> parseOptionalDate(dateText, "dd MMM yyyy")) + .map(date -> new DateWrapper(date.atStartOfDay(), true)) + .orElseThrow(() -> + new ParsingException("Could not parse upload date \"" + dateText + "\"")); + } + + private Optional parseOptionalDate(final String date, final String pattern) { + try { + // TODO: this parses English formatted dates only, we need a better approach to parse + // the textual date + final var formatter = DateTimeFormatter.ofPattern(pattern, Locale.ENGLISH); + return Optional.of(LocalDate.parse(date, formatter)); + } catch (final DateTimeParseException e) { + return Optional.empty(); + } } @Nonnull diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemExtractor.java index 98730c39ed..7b4deaaa58 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemExtractor.java @@ -44,8 +44,8 @@ import javax.annotation.Nullable; import java.time.Instant; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; +import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.regex.Pattern; @@ -247,12 +247,14 @@ public boolean isUploaderVerified() throws ParsingException { @Nullable @Override public String getTextualUploadDate() throws ParsingException { - if (getStreamType().equals(StreamType.LIVE_STREAM)) { + if (getStreamType() == StreamType.LIVE_STREAM) { return null; } if (isPremiere()) { - return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").format(getDateFromPremiere()); + final var localDateTime = LocalDateTime.ofInstant(getInstantFromPremiere(), + ZoneId.systemDefault()); + return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").format(localDateTime); } String publishedTimeText = getTextFromObject(videoInfo.getObject("publishedTimeText")); @@ -273,12 +275,12 @@ public String getTextualUploadDate() throws ParsingException { @Nullable @Override public DateWrapper getUploadDate() throws ParsingException { - if (getStreamType().equals(StreamType.LIVE_STREAM)) { + if (getStreamType() == StreamType.LIVE_STREAM) { return null; } if (isPremiere()) { - return new DateWrapper(getDateFromPremiere()); + return new DateWrapper(getInstantFromPremiere()); } final String textualUploadDate = getTextualUploadDate(); @@ -402,15 +404,15 @@ private boolean isPremiere() { return isPremiere; } - private OffsetDateTime getDateFromPremiere() throws ParsingException { + private Instant getInstantFromPremiere() throws ParsingException { final JsonObject upcomingEventData = videoInfo.getObject("upcomingEventData"); final String startTime = upcomingEventData.getString("startTime"); try { - return OffsetDateTime.ofInstant(Instant.ofEpochSecond(Long.parseLong(startTime)), - ZoneOffset.UTC); + return Instant.ofEpochSecond(Long.parseLong(startTime)); } catch (final Exception e) { - throw new ParsingException("Could not parse date from premiere: \"" + startTime + "\""); + final String message = "Could not parse date from premiere: \"" + startTime + "\""; + throw new ParsingException(message, e); } } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemLockupExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemLockupExtractor.java index f685f33dec..416b9a7258 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemLockupExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemLockupExtractor.java @@ -18,7 +18,6 @@ import org.schabi.newpipe.extractor.utils.Utils; import java.time.LocalDateTime; -import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -292,8 +291,8 @@ public DateWrapper getUploadDate() throws ParsingException { try { // As we request a UTC offset of 0 minutes, we get the UTC date - return new DateWrapper(OffsetDateTime.of(LocalDateTime.parse( - premiereDate, PREMIERES_DATE_FORMATTER), ZoneOffset.UTC)); + final var dateTime = LocalDateTime.parse(premiereDate, PREMIERES_DATE_FORMATTER); + return new DateWrapper(dateTime.atZone(ZoneOffset.UTC).toInstant(), false); } catch (final DateTimeParseException e) { throw new ParsingException("Could not parse premiere upload date", e); } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/kiosk/YoutubeChartsBaseKioskExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/kiosk/YoutubeChartsBaseKioskExtractor.java index 3ede87619e..f5618a342a 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/kiosk/YoutubeChartsBaseKioskExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/kiosk/YoutubeChartsBaseKioskExtractor.java @@ -27,7 +27,7 @@ import javax.annotation.Nullable; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.time.OffsetDateTime; +import java.time.LocalDate; import java.time.ZoneOffset; import java.util.HashMap; import java.util.List; @@ -203,20 +203,15 @@ public String getTextualUploadDate() { @Nonnull @Override public DateWrapper getUploadDate() { - final JsonObject releaseDate = videoObject.getObject("releaseDate"); - return new DateWrapper(OffsetDateTime.of( - releaseDate.getInt("year"), - releaseDate.getInt("month"), - releaseDate.getInt("day"), - 0, - 0, - 0, - 0, - // We request that times should be returned with 0 offset to UTC timezone in - // the JSON body, but YouTube charts does it only in its HTTP headers - ZoneOffset.UTC), - // We don't have more info than the release day - true); + final var releaseDate = videoObject.getObject("releaseDate"); + final var localDate = LocalDate.of(releaseDate.getInt("year"), + releaseDate.getInt("month"), releaseDate.getInt("day")); + // We request that times should be returned with 0 offset to UTC timezone in + // the JSON body, but YouTube charts does it only in its HTTP headers + final var instant = localDate.atStartOfDay(ZoneOffset.UTC).toInstant(); + + // We don't have more info than the release day, hence isApproximate=true + return new DateWrapper(instant, true); } @Override diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamExtractor.java index f54ed177da..63650a7906 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamExtractor.java @@ -39,6 +39,7 @@ import javax.annotation.Nullable; import java.io.IOException; +import java.time.Instant; import java.util.Collections; import java.util.List; import java.util.Locale; @@ -71,7 +72,7 @@ public String getTextualUploadDate() throws ParsingException { } /** - * A more general {@code Calendar} instance set to the date provided by the service.
+ * A more general {@link Instant} instance set to the date provided by the service.
* Implementations usually will just parse the date returned from the {@link * #getTextualUploadDate()}. * diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/localization/TimeAgoParserTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/localization/TimeAgoParserTest.java index db45e807b9..451b66ac35 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/localization/TimeAgoParserTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/localization/TimeAgoParserTest.java @@ -11,10 +11,8 @@ import java.time.Duration; import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; +import java.time.Month; import java.time.temporal.ChronoUnit; -import java.util.Objects; import java.util.function.Function; import java.util.stream.Stream; @@ -41,13 +39,9 @@ public static Stream parseTimeAgo() { @ParameterizedTest @MethodSource void parseTimeAgo(final ParseTimeAgoTestData testData) { - final OffsetDateTime now = OffsetDateTime.of( - LocalDateTime.of(2020, 1, 1, 1, 1, 1), - ZoneOffset.UTC); - final TimeAgoParser parser = Objects.requireNonNull( - TimeAgoPatternsManager.getTimeAgoParserFor(Localization.DEFAULT, now)); - - final OffsetDateTime expected = testData.getExpectedApplyToNow().apply(now); + final var now = LocalDateTime.of(2020, Month.JANUARY, 1, 1, 1, 1); + final var parser = TimeAgoPatternsManager.getTimeAgoParserFor(Localization.DEFAULT, now); + final var expected = testData.getExpectedApplyToNow().apply(now); assertAll( Stream.of( @@ -55,7 +49,7 @@ void parseTimeAgo(final ParseTimeAgoTestData testData) { testData.getTextualDateShort()) .map(textualDate -> () -> assertEquals( expected, - parser.parse(textualDate).offsetDateTime(), + parser.parse(textualDate).getLocalDateTime(), "Expected " + expected + " for " + textualDate )) ); @@ -63,12 +57,12 @@ void parseTimeAgo(final ParseTimeAgoTestData testData) { static class ParseTimeAgoTestData { public static final String AGO_SUFFIX = " ago"; - private final Function expectedApplyToNow; + private final Function expectedApplyToNow; private final String textualDateLong; private final String textualDateShort; ParseTimeAgoTestData( - final Function expectedApplyToNow, + final Function expectedApplyToNow, final String textualDateLong, final String textualDateShort ) { @@ -89,17 +83,17 @@ public static ParseTimeAgoTestData lessThanDay( } public static ParseTimeAgoTestData greaterThanDay( - final Function expectedApplyToNow, + final Function expectedApplyToNow, final String textualDateLong, final String textualDateShort ) { return new ParseTimeAgoTestData( - d -> expectedApplyToNow.apply(d).truncatedTo(ChronoUnit.HOURS), + expectedApplyToNow.andThen(d -> d.truncatedTo(ChronoUnit.DAYS)), textualDateLong + AGO_SUFFIX, textualDateShort + AGO_SUFFIX); } - public Function getExpectedApplyToNow() { + public Function getExpectedApplyToNow() { return expectedApplyToNow; } diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/DefaultStreamExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/DefaultStreamExtractorTest.java index 0d04157efc..b27343ee56 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/DefaultStreamExtractorTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/DefaultStreamExtractorTest.java @@ -5,7 +5,6 @@ import org.schabi.newpipe.extractor.InfoItemsCollector; import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.MetaInfo; -import org.schabi.newpipe.extractor.localization.DateWrapper; import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.ContentAvailability; import org.schabi.newpipe.extractor.stream.Description; @@ -17,6 +16,7 @@ import javax.annotation.Nullable; import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.net.MalformedURLException; import java.net.URL; @@ -195,18 +195,18 @@ public void testViewCount() throws Exception { @Test @Override public void testUploadDate() throws Exception { - final DateWrapper dateWrapper = extractor().getUploadDate(); + final var dateWrapper = extractor().getUploadDate(); + final var expectedDate = expectedUploadDate(); - if (expectedUploadDate() == null) { + if (expectedDate == null) { assertNull(dateWrapper); } else { assertNotNull(dateWrapper); - final LocalDateTime expectedDateTime = LocalDateTime.parse(expectedUploadDate(), + final var expectedDateTime = LocalDateTime.parse(expectedDate, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")); - final LocalDateTime actualDateTime = dateWrapper.offsetDateTime().toLocalDateTime(); - assertEquals(expectedDateTime, actualDateTime); + assertEquals(expectedDateTime, dateWrapper.getLocalDateTime(ZoneOffset.UTC)); } } diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampRadioStreamExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampRadioStreamExtractorTest.java index 78d490bd92..378da8053c 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampRadioStreamExtractorTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampRadioStreamExtractorTest.java @@ -19,10 +19,11 @@ import org.schabi.newpipe.extractor.stream.StreamType; import java.io.IOException; -import java.util.Calendar; +import java.time.LocalDate; +import java.time.Month; +import java.time.ZoneOffset; import java.util.Collections; import java.util.List; -import java.util.TimeZone; public class BandcampRadioStreamExtractorTest extends DefaultStreamExtractorTest { @@ -82,14 +83,10 @@ public List expectedDescriptionContains() { @Override @Test public void testUploadDate() throws ParsingException { - final Calendar expectedCalendar = Calendar.getInstance(); - - // 16 May 2017 00:00:00 GMT - expectedCalendar.setTimeZone(TimeZone.getTimeZone("GMT")); - expectedCalendar.setTimeInMillis(0); - expectedCalendar.set(2017, Calendar.MAY, 16); - - assertEquals(expectedCalendar.getTimeInMillis(), extractor().getUploadDate().offsetDateTime().toInstant().toEpochMilli()); + final var expectedDate = LocalDate.of(2017, Month.MAY, 16); + final var actualDate = extractor().getUploadDate().getLocalDateTime(ZoneOffset.UTC) + .toLocalDate(); + assertEquals(expectedDate, actualDate); } @Test diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamExtractorTest.java index d7fab32d1f..3d67efcadc 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamExtractorTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamExtractorTest.java @@ -6,6 +6,7 @@ import static org.schabi.newpipe.extractor.ServiceList.SoundCloud; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.schabi.newpipe.extractor.ExtractorAsserts; import org.schabi.newpipe.extractor.MediaFormat; @@ -27,7 +28,8 @@ public class SoundcloudStreamExtractorTest { private static final String SOUNDCLOUD = "https://soundcloud.com/"; - public static class SoundcloudGeoRestrictedTrack extends DefaultStreamExtractorTest { + @Nested + class SoundcloudGeoRestrictedTrack extends DefaultStreamExtractorTest { private static final String ID = "one-touch"; private static final String UPLOADER = SOUNDCLOUD + "jessglynne"; private static final int TIMESTAMP = 0; @@ -63,7 +65,7 @@ protected void fetchExtractor(final StreamExtractor extractor) throws Exception @Override public long expectedTimestamp() { return TIMESTAMP; } @Override public long expectedViewCountAtLeast() { return 43000; } @Nullable @Override public String expectedUploadDate() { return "2019-05-16 16:28:45.000"; } - @Nullable @Override public String expectedTextualUploadDate() { return "2019-05-16 16:28:45"; } + @Nullable @Override public String expectedTextualUploadDate() { return "2019-05-16T16:28:45Z"; } @Override public long expectedLikeCountAtLeast() { return 600; } @Override public long expectedDislikeCountAtLeast() { return -1; } @Override public boolean expectedHasAudioStreams() { return false; } @@ -82,7 +84,8 @@ public void testRelatedItems() throws Exception { } } - public static class SoundcloudGoPlusTrack extends DefaultStreamExtractorTest { + @Nested + class SoundcloudGoPlusTrack extends DefaultStreamExtractorTest { private static final String ID = "places"; private static final String UPLOADER = SOUNDCLOUD + "martinsolveig"; private static final int TIMESTAMP = 0; @@ -127,7 +130,7 @@ public void testRelatedItems() throws Exception { @Override public long expectedTimestamp() { return TIMESTAMP; } @Override public long expectedViewCountAtLeast() { return 386000; } @Nullable @Override public String expectedUploadDate() { return "2016-11-11 01:16:37.000"; } - @Nullable @Override public String expectedTextualUploadDate() { return "2016-11-11 01:16:37"; } + @Nullable @Override public String expectedTextualUploadDate() { return "2016-11-11T01:16:37Z"; } @Override public long expectedLikeCountAtLeast() { return 7350; } @Override public long expectedDislikeCountAtLeast() { return -1; } @Override public boolean expectedHasAudioStreams() { return false; } @@ -139,7 +142,8 @@ public void testRelatedItems() throws Exception { @Override public String expectedCategory() { return "Dance"; } } - static class CreativeCommonsOpenMindsEp21 extends DefaultStreamExtractorTest { + @Nested + class CreativeCommonsOpenMindsEp21 extends DefaultStreamExtractorTest { private static final String ID = "open-minds-ep-21-dr-beth-harris-and-dr-steven-zucker-of-smarthistory"; private static final String UPLOADER = SOUNDCLOUD + "wearecc"; private static final int TIMESTAMP = 69; @@ -167,7 +171,7 @@ protected StreamExtractor createExtractor() throws Exception { @Override public long expectedTimestamp() { return TIMESTAMP; } @Override public long expectedViewCountAtLeast() { return 15000; } @Nullable @Override public String expectedUploadDate() { return "2022-10-03 18:49:49.000"; } - @Nullable @Override public String expectedTextualUploadDate() { return "2022-10-03 18:49:49"; } + @Nullable @Override public String expectedTextualUploadDate() { return "2022-10-03T18:49:49Z"; } @Override public long expectedLikeCountAtLeast() { return 10; } @Override public long expectedDislikeCountAtLeast() { return -1; } @Override public boolean expectedHasRelatedItems() { return false; } diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelLocalizationTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelLocalizationTest.java index 6633a76f89..cae661f8e1 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelLocalizationTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelLocalizationTest.java @@ -73,9 +73,8 @@ private void testLocalizationsFor(final String channelUrl) throws Exception { + "\n:::: " + item.getStreamType() + ", views = " + item.getViewCount(); final DateWrapper uploadDate = item.getUploadDate(); if (uploadDate != null) { - final String dateAsText = dateTimeFormatter.format(uploadDate.offsetDateTime()); - debugMessage += "\n:::: " + item.getTextualUploadDate() + - "\n:::: " + dateAsText; + final String dateStr = dateTimeFormatter.format(uploadDate.getLocalDateTime()); + debugMessage += "\n:::: " + item.getTextualUploadDate() + "\n:::: " + dateStr; } if (DEBUG) System.out.println(debugMessage + "\n"); }