Skip to content

Commit 39b9482

Browse files
Merge pull request #1372 from Isira-Seneviratne/Refactor-date-parsing
Refactor date parsing
2 parents 8dfb0d3 + 2b306a3 commit 39b9482

29 files changed

Lines changed: 242 additions & 325 deletions
Lines changed: 76 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,78 @@
11
package org.schabi.newpipe.extractor.localization;
22

3+
import org.schabi.newpipe.extractor.exceptions.ParsingException;
34

45
import javax.annotation.Nonnull;
6+
import javax.annotation.Nullable;
57
import java.io.Serializable;
8+
import java.time.Instant;
9+
import java.time.LocalDateTime;
610
import java.time.OffsetDateTime;
11+
import java.time.ZoneId;
712
import java.time.ZoneOffset;
8-
import java.util.Calendar;
9-
import java.util.GregorianCalendar;
13+
import java.time.format.DateTimeParseException;
1014

1115
/**
1216
* A wrapper class that provides a field to describe if the date/time is precise or just an
1317
* approximation.
1418
*/
1519
public class DateWrapper implements Serializable {
1620
@Nonnull
17-
private final OffsetDateTime offsetDateTime;
21+
private final Instant instant;
1822
private final boolean isApproximation;
1923

20-
/**
21-
* @deprecated Use {@link #DateWrapper(OffsetDateTime)} instead.
22-
*/
23-
@Deprecated
24-
public DateWrapper(@Nonnull final Calendar calendar) {
25-
//noinspection deprecation
26-
this(calendar, false);
27-
}
28-
29-
/**
30-
* @deprecated Use {@link #DateWrapper(OffsetDateTime, boolean)} instead.
31-
*/
32-
@Deprecated
33-
public DateWrapper(@Nonnull final Calendar calendar, final boolean isApproximation) {
34-
this(OffsetDateTime.ofInstant(calendar.toInstant(), ZoneOffset.UTC), isApproximation);
35-
}
36-
3724
public DateWrapper(@Nonnull final OffsetDateTime offsetDateTime) {
3825
this(offsetDateTime, false);
3926
}
4027

4128
public DateWrapper(@Nonnull final OffsetDateTime offsetDateTime,
4229
final boolean isApproximation) {
43-
this.offsetDateTime = offsetDateTime.withOffsetSameInstant(ZoneOffset.UTC);
30+
this(offsetDateTime.toInstant(), isApproximation);
31+
}
32+
33+
public DateWrapper(@Nonnull final Instant instant) {
34+
this(instant, false);
35+
}
36+
37+
public DateWrapper(@Nonnull final Instant instant, final boolean isApproximation) {
38+
this.instant = instant;
4439
this.isApproximation = isApproximation;
4540
}
4641

42+
public DateWrapper(@Nonnull final LocalDateTime dateTime, final boolean isApproximation) {
43+
this(dateTime.atZone(ZoneId.systemDefault()).toInstant(), isApproximation);
44+
}
45+
4746
/**
48-
* @return the wrapped date/time as a {@link Calendar}.
49-
* @deprecated use {@link #offsetDateTime()} instead.
47+
* @return the wrapped {@link Instant}
5048
*/
51-
@Deprecated
5249
@Nonnull
53-
public Calendar date() {
54-
return GregorianCalendar.from(offsetDateTime.toZonedDateTime());
50+
public Instant getInstant() {
51+
return instant;
5552
}
5653

5754
/**
58-
* @return the wrapped date/time.
55+
* @return the wrapped {@link Instant} as an {@link OffsetDateTime} set to UTC.
5956
*/
6057
@Nonnull
6158
public OffsetDateTime offsetDateTime() {
62-
return offsetDateTime;
59+
return instant.atOffset(ZoneOffset.UTC);
60+
}
61+
62+
/**
63+
* @return the wrapped {@link Instant} as a {@link LocalDateTime} in the current time zone.
64+
*/
65+
@Nonnull
66+
public LocalDateTime getLocalDateTime() {
67+
return getLocalDateTime(ZoneId.systemDefault());
68+
}
69+
70+
/**
71+
* @return the wrapped {@link Instant} as a {@link LocalDateTime} in the given time zone.
72+
*/
73+
@Nonnull
74+
public LocalDateTime getLocalDateTime(@Nonnull final ZoneId zoneId) {
75+
return LocalDateTime.ofInstant(instant, zoneId);
6376
}
6477

6578
/**
@@ -73,8 +86,42 @@ public boolean isApproximation() {
7386
@Override
7487
public String toString() {
7588
return "DateWrapper{"
76-
+ "offsetDateTime=" + offsetDateTime
89+
+ "instant=" + instant
7790
+ ", isApproximation=" + isApproximation
7891
+ '}';
7992
}
93+
94+
/**
95+
* Parses a date string that matches the ISO-8601 {@link OffsetDateTime} pattern, e.g.
96+
* "2011-12-03T10:15:30+01:00".
97+
*
98+
* @param date The date string
99+
* @return a non-approximate {@link DateWrapper}, or null if the string is null
100+
* @throws ParsingException if the string does not match the expected format
101+
*/
102+
@Nullable
103+
public static DateWrapper fromOffsetDateTime(final String date) throws ParsingException {
104+
try {
105+
return date != null ? new DateWrapper(OffsetDateTime.parse(date)) : null;
106+
} catch (final DateTimeParseException e) {
107+
throw new ParsingException("Could not parse date: \"" + date + "\"", e);
108+
}
109+
}
110+
111+
/**
112+
* Parses a date string that matches the ISO-8601 {@link Instant} pattern, e.g.
113+
* "2011-12-03T10:15:30Z".
114+
*
115+
* @param date The date string
116+
* @return a non-approximate {@link DateWrapper}, or null if the string is null
117+
* @throws ParsingException if the string does not match the expected format
118+
*/
119+
@Nullable
120+
public static DateWrapper fromInstant(final String date) throws ParsingException {
121+
try {
122+
return date != null ? new DateWrapper(Instant.parse(date)) : null;
123+
} catch (final DateTimeParseException e) {
124+
throw new ParsingException("Could not parse date: \"" + date + "\"", e);
125+
}
126+
}
80127
}

extractor/src/main/java/org/schabi/newpipe/extractor/localization/TimeAgoParser.java

Lines changed: 10 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
import org.schabi.newpipe.extractor.timeago.PatternsHolder;
55
import org.schabi.newpipe.extractor.utils.Parser;
66

7-
import java.time.OffsetDateTime;
8-
import java.time.ZoneOffset;
7+
import java.time.LocalDateTime;
98
import java.time.temporal.ChronoUnit;
109
import java.util.Map;
1110
import java.util.regex.Pattern;
@@ -16,20 +15,7 @@
1615
*/
1716
public class TimeAgoParser {
1817
private final PatternsHolder patternsHolder;
19-
private final OffsetDateTime now;
20-
21-
/**
22-
* Creates a helper to parse upload dates in the format '2 days ago'.
23-
* <p>
24-
* Instantiate a new {@link TimeAgoParser} every time you extract a new batch of items.
25-
* </p>
26-
*
27-
* @param patternsHolder An object that holds the "time ago" patterns, special cases, and the
28-
* language word separator.
29-
*/
30-
public TimeAgoParser(final PatternsHolder patternsHolder) {
31-
this(patternsHolder, OffsetDateTime.now(ZoneOffset.UTC));
32-
}
18+
private final LocalDateTime now;
3319

3420
/**
3521
* Creates a helper to parse upload dates in the format '2 days ago'.
@@ -41,7 +27,7 @@ public TimeAgoParser(final PatternsHolder patternsHolder) {
4127
* language word separator.
4228
* @param now The current time
4329
*/
44-
public TimeAgoParser(final PatternsHolder patternsHolder, final OffsetDateTime now) {
30+
public TimeAgoParser(final PatternsHolder patternsHolder, final LocalDateTime now) {
4531
this.patternsHolder = patternsHolder;
4632
this.now = now;
4733
}
@@ -118,34 +104,13 @@ private boolean textualDateMatches(final String textualDate, final String agoPhr
118104
}
119105

120106
private DateWrapper getResultFor(final int timeAgoAmount, final ChronoUnit chronoUnit) {
121-
OffsetDateTime offsetDateTime = now;
122-
boolean isApproximation = false;
123-
124-
switch (chronoUnit) {
125-
case SECONDS:
126-
case MINUTES:
127-
case HOURS:
128-
offsetDateTime = offsetDateTime.minus(timeAgoAmount, chronoUnit);
129-
break;
130-
131-
case DAYS:
132-
case WEEKS:
133-
case MONTHS:
134-
offsetDateTime = offsetDateTime.minus(timeAgoAmount, chronoUnit);
135-
isApproximation = true;
136-
break;
137-
138-
case YEARS:
107+
final var localDateTime = chronoUnit == ChronoUnit.YEARS
139108
// minusDays is needed to prevent `PrettyTime` from showing '12 months ago'.
140-
offsetDateTime = offsetDateTime.minusYears(timeAgoAmount).minusDays(1);
141-
isApproximation = true;
142-
break;
143-
}
144-
145-
if (isApproximation) {
146-
offsetDateTime = offsetDateTime.truncatedTo(ChronoUnit.HOURS);
147-
}
148-
149-
return new DateWrapper(offsetDateTime, isApproximation);
109+
? now.minusYears(timeAgoAmount).minusDays(1)
110+
: now.minus(timeAgoAmount, chronoUnit);
111+
final boolean isApproximate = chronoUnit.isDateBased();
112+
final var resolvedDateTime =
113+
isApproximate ? localDateTime.truncatedTo(ChronoUnit.DAYS) : localDateTime;
114+
return new DateWrapper(resolvedDateTime, isApproximate);
150115
}
151116
}

extractor/src/main/java/org/schabi/newpipe/extractor/localization/TimeAgoPatternsManager.java

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import org.schabi.newpipe.extractor.timeago.PatternsHolder;
44
import org.schabi.newpipe.extractor.timeago.PatternsManager;
55

6-
import java.time.OffsetDateTime;
6+
import java.time.LocalDateTime;
77

88
import javax.annotation.Nonnull;
99
import javax.annotation.Nullable;
@@ -20,25 +20,13 @@ private static PatternsHolder getPatternsFor(@Nonnull final Localization localiz
2020

2121
@Nullable
2222
public static TimeAgoParser getTimeAgoParserFor(@Nonnull final Localization localization) {
23-
final PatternsHolder holder = getPatternsFor(localization);
24-
25-
if (holder == null) {
26-
return null;
27-
}
28-
29-
return new TimeAgoParser(holder);
23+
return getTimeAgoParserFor(localization, LocalDateTime.now());
3024
}
3125

3226
@Nullable
33-
public static TimeAgoParser getTimeAgoParserFor(
34-
@Nonnull final Localization localization,
35-
@Nonnull final OffsetDateTime now) {
27+
public static TimeAgoParser getTimeAgoParserFor(@Nonnull final Localization localization,
28+
@Nonnull final LocalDateTime now) {
3629
final PatternsHolder holder = getPatternsFor(localization);
37-
38-
if (holder == null) {
39-
return null;
40-
}
41-
42-
return new TimeAgoParser(holder, now);
30+
return holder == null ? null : new TimeAgoParser(holder, now);
4331
}
4432
}

extractor/src/main/java/org/schabi/newpipe/extractor/services/bandcamp/extractors/BandcampExtractorHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ public static DateWrapper parseDate(final String textDate) throws ParsingExcepti
204204
try {
205205
final ZonedDateTime zonedDateTime = ZonedDateTime.parse(textDate,
206206
DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm:ss zzz", Locale.ENGLISH));
207-
return new DateWrapper(zonedDateTime.toOffsetDateTime(), false);
207+
return new DateWrapper(zonedDateTime.toInstant());
208208
} catch (final DateTimeException e) {
209209
throw new ParsingException("Could not parse date '" + textDate + "'", e);
210210
}

extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCParsingHelper.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,12 @@
88
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
99
import org.schabi.newpipe.extractor.downloader.Downloader;
1010
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
11-
import org.schabi.newpipe.extractor.exceptions.ParsingException;
1211
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
1312
import org.schabi.newpipe.extractor.localization.Localization;
1413

1514
import javax.annotation.Nonnull;
1615
import javax.annotation.Nullable;
1716
import java.io.IOException;
18-
import java.time.OffsetDateTime;
19-
import java.time.format.DateTimeParseException;
2017
import java.util.ArrayList;
2118
import java.util.Collections;
2219
import java.util.List;
@@ -33,15 +30,6 @@ public final class MediaCCCParsingHelper {
3330

3431
private MediaCCCParsingHelper() { }
3532

36-
public static OffsetDateTime parseDateFrom(final String textualUploadDate)
37-
throws ParsingException {
38-
try {
39-
return OffsetDateTime.parse(textualUploadDate);
40-
} catch (final DateTimeParseException e) {
41-
throw new ParsingException("Could not parse date: \"" + textualUploadDate + "\"", e);
42-
}
43-
}
44-
4533
/**
4634
* Check whether an id is a live stream id
4735
* @param id the {@code id} to check

extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCRecentKiosk.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,10 @@ public InfoItemsPage<StreamInfoItem> getInitialPage() throws IOException, Extrac
5252

5353
// Streams in the recent kiosk are not ordered by the release date.
5454
// Sort them to have the latest stream at the beginning of the list.
55-
final Comparator<StreamInfoItem> comparator = Comparator
56-
.comparing(StreamInfoItem::getUploadDate, Comparator
57-
.nullsLast(Comparator.comparing(DateWrapper::offsetDateTime)))
55+
final var comparator = Comparator.comparing(StreamInfoItem::getUploadDate,
56+
Comparator.nullsLast(Comparator.comparing(DateWrapper::getInstant)))
5857
.reversed();
59-
final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId(),
60-
comparator);
58+
final var collector = new StreamInfoItemsCollector(getServiceId(), comparator);
6159

6260
events.stream()
6361
.filter(JsonObject.class::isInstance)

extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCRecentKioskExtractor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,6 @@ public String getTextualUploadDate() throws ParsingException {
9292
public DateWrapper getUploadDate() throws ParsingException {
9393
final ZonedDateTime zonedDateTime = ZonedDateTime.parse(event.getString("date"),
9494
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSzzzz"));
95-
return new DateWrapper(zonedDateTime.toOffsetDateTime(), false);
95+
return new DateWrapper(zonedDateTime.toInstant());
9696
}
9797
}

extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getImageListFromLogoImageUrl;
44
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getThumbnailsFromStreamItem;
5-
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.parseDateFrom;
65
import static org.schabi.newpipe.extractor.stream.AudioStream.UNKNOWN_BITRATE;
76
import static org.schabi.newpipe.extractor.stream.Stream.ID_UNKNOWN;
87

@@ -37,6 +36,7 @@
3736
import java.util.Locale;
3837

3938
import javax.annotation.Nonnull;
39+
import javax.annotation.Nullable;
4040

4141
public class MediaCCCStreamExtractor extends StreamExtractor {
4242
private JsonObject data;
@@ -46,16 +46,16 @@ public MediaCCCStreamExtractor(final StreamingService service, final LinkHandler
4646
super(service, linkHandler);
4747
}
4848

49-
@Nonnull
49+
@Nullable
5050
@Override
5151
public String getTextualUploadDate() {
5252
return data.getString("release_date");
5353
}
5454

55-
@Nonnull
55+
@Nullable
5656
@Override
5757
public DateWrapper getUploadDate() throws ParsingException {
58-
return new DateWrapper(parseDateFrom(getTextualUploadDate()));
58+
return DateWrapper.fromOffsetDateTime(getTextualUploadDate());
5959
}
6060

6161
@Nonnull

extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/infoItems/MediaCCCStreamInfoItemExtractor.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import org.schabi.newpipe.extractor.Image;
55
import org.schabi.newpipe.extractor.exceptions.ParsingException;
66
import org.schabi.newpipe.extractor.localization.DateWrapper;
7-
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper;
87
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
98
import org.schabi.newpipe.extractor.stream.StreamType;
109

@@ -65,11 +64,8 @@ public String getTextualUploadDate() {
6564
@Nullable
6665
@Override
6766
public DateWrapper getUploadDate() throws ParsingException {
68-
final String date = getTextualUploadDate();
69-
if (date == null) {
70-
return null; // event is in the future...
71-
}
72-
return new DateWrapper(MediaCCCParsingHelper.parseDateFrom(date));
67+
// if null, event is in the future...
68+
return DateWrapper.fromOffsetDateTime(getTextualUploadDate());
7369
}
7470

7571
@Override

0 commit comments

Comments
 (0)