Skip to content

Refactor date parsing#1372

Merged
Isira-Seneviratne merged 21 commits intoTeamNewPipe:devfrom
Isira-Seneviratne:Refactor-date-parsing
Oct 10, 2025
Merged

Refactor date parsing#1372
Isira-Seneviratne merged 21 commits intoTeamNewPipe:devfrom
Isira-Seneviratne:Refactor-date-parsing

Conversation

@Isira-Seneviratne
Copy link
Copy Markdown
Member

@Isira-Seneviratne Isira-Seneviratne commented Sep 12, 2025

  • I carefully read the contribution guidelines and agree to them.
  • I have tested the API against NewPipe.
  • I agree to create a pull request for NewPipe as soon as possible to make it compatible with the changed API.

  • Update the DateWrapper class to store an Instant, and remove the deprecated methods.
  • [YouTube] Improve the upload date parsing so that non-approximate dates can be returned.
  • [SoundCloud] Return the original instant string in the stream extractor.

Copy link
Copy Markdown
Contributor

@TobiGr TobiGr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks

Comment on lines +177 to +188
final String dateStr = playerMicroFormatRenderer.getString("uploadDate",
playerMicroFormatRenderer.getString("publishDate", ""));
if (!dateStr.isEmpty()) {
return dateStr;
}

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()) {
// a running live stream
return liveDetails.getString("startTimestamp");
final var liveDetails = playerMicroFormatRenderer.getObject("liveBroadcastDetails");
final String timestamp = liveDetails.getString("endTimestamp", // an ended live stream
liveDetails.getString("startTimestamp", "")); // a running live stream

if (!timestamp.isEmpty()) {
return timestamp;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes it harder to understand what's going on. I'd prefer to keep it readable and revert this change.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like the code below line 180 as a whole could be removed. I checked how the date is being extracted, and it's always taken from uploadDate or publishDate.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YouTube might return different layouts in different cases, so it's better to keep all cases

@TobiGr
Copy link
Copy Markdown
Contributor

TobiGr commented Sep 17, 2025

Please list all the breaking changes in the PR description so they can be copied to the release notes.

@TobiGr
Copy link
Copy Markdown
Contributor

TobiGr commented Sep 17, 2025

The tests are now dependent on the runner's time zone, which is not good. While the automated tests pass (server is set to UTC), tests on my computer fail:

results

YoutubeStreamExtractorControversialTest > testUploadDate() FAILED
    org.opentest4j.AssertionFailedError: expected: <2010-09-09T15:40:44> but was: <2010-09-09T17:40:44>
        at app//org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest.testUploadDate(DefaultStreamExtractorTest.java:206)
        at java.base@17.0.16/java.lang.reflect.Method.invoke(Method.java:569)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)

YoutubeStreamExtractorDefaultTest$DescriptionTestPewdiepie > testUploadDate() FAILED
    org.opentest4j.AssertionFailedError: expected: <2019-08-24T15:39:57> but was: <2019-08-24T17:39:57>
        at app//org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest.testUploadDate(DefaultStreamExtractorTest.java:206)
        at java.base@17.0.16/java.lang.reflect.Method.invoke(Method.java:569)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)

YoutubeStreamExtractorDefaultTest$DescriptionTestUnboxing > testUploadDate() FAILED
    org.opentest4j.AssertionFailedError: expected: <2018-06-19T19:41:34> but was: <2018-06-19T21:41:34>
        at app//org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest.testUploadDate(DefaultStreamExtractorTest.java:206)
        at java.base@17.0.16/java.lang.reflect.Method.invoke(Method.java:569)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)

YoutubeStreamExtractorDefaultTest$NoVisualMetadataVideoTest > testUploadDate() FAILED
    org.opentest4j.AssertionFailedError: expected: <2017-05-16T14:50:53> but was: <2017-05-16T16:50:53>
        at app//org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest.testUploadDate(DefaultStreamExtractorTest.java:206)
        at java.base@17.0.16/java.lang.reflect.Method.invoke(Method.java:569)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)

YoutubeStreamExtractorDefaultTest$PublicBroadcasterTest > testUploadDate() FAILED
    org.opentest4j.AssertionFailedError: expected: <2023-07-07T15:30:08> but was: <2023-07-07T17:30:08>
        at app//org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest.testUploadDate(DefaultStreamExtractorTest.java:206)
        at java.base@17.0.16/java.lang.reflect.Method.invoke(Method.java:569)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)

YoutubeStreamExtractorDefaultTest$RatingsDisabledTest > testUploadDate() FAILED
    org.opentest4j.AssertionFailedError: expected: <2023-01-13T21:53:57> but was: <2023-01-13T22:53:57>
        at app//org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest.testUploadDate(DefaultStreamExtractorTest.java:206)
        at java.base@17.0.16/java.lang.reflect.Method.invoke(Method.java:569)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)

YoutubeStreamExtractorDefaultTest$StreamSegmentsTestMaiLab > testUploadDate() FAILED
    org.opentest4j.AssertionFailedError: expected: <2020-11-19T05:30:01> but was: <2020-11-19T06:30:01>
        at app//org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest.testUploadDate(DefaultStreamExtractorTest.java:206)
        at java.base@17.0.16/java.lang.reflect.Method.invoke(Method.java:569)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)

YoutubeStreamExtractorDefaultTest$StreamSegmentsTestMaiLab > testName() SKIPPED

YoutubeStreamExtractorDefaultTest$StreamSegmentsTestMaiLab > testTags() SKIPPED

YoutubeStreamExtractorDefaultTest$StreamSegmentsTestTagesschau > testUploadDate() FAILED
    org.opentest4j.AssertionFailedError: expected: <2021-03-17T19:56:59> but was: <2021-03-17T20:56:59>
        at app//org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest.testUploadDate(DefaultStreamExtractorTest.java:206)
        at java.base@17.0.16/java.lang.reflect.Method.invoke(Method.java:569)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)

YoutubeStreamExtractorLivestreamTest > testUploadDate() FAILED
    org.opentest4j.AssertionFailedError: expected: <2022-07-12T12:12:29> but was: <2022-07-12T14:12:29>
        at app//org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest.testUploadDate(DefaultStreamExtractorTest.java:206)
        at java.base@17.0.16/java.lang.reflect.Method.invoke(Method.java:569)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)

YoutubeStreamExtractorRelatedMixTest > testUploadDate() FAILED
    org.opentest4j.AssertionFailedError: expected: <2015-07-09T16:34:35> but was: <2015-07-09T18:34:35>
        at app//org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest.testUploadDate(DefaultStreamExtractorTest.java:206)
        at java.base@17.0.16/java.lang.reflect.Method.invoke(Method.java:569)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)

YoutubeStreamExtractorUnlistedTest > testUploadDate() FAILED
    org.opentest4j.AssertionFailedError: expected: <2017-09-22T12:15:21> but was: <2017-09-22T14:15:21>
        at app//org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest.testUploadDate(DefaultStreamExtractorTest.java:206)
        at java.base@17.0.16/java.lang.reflect.Method.invoke(Method.java:569)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)
        at java.base@17.0.16/java.util.ArrayList.forEach(ArrayList.java:1511)


@Stypox
Copy link
Copy Markdown
Member

Stypox commented Sep 17, 2025

Mmmh, what's the advantage of using Instant instead of OffsetDateTime? I see this PR does many changes but I don't see clear advantages. I mean, sure, some code is shorter, but the longer code we had before worked just fine, or was there some limitation I am not getting?

@Isira-Seneviratne
Copy link
Copy Markdown
Member Author

Mmmh, what's the advantage of using Instant instead of OffsetDateTime? I see this PR does many changes but I don't see clear advantages. I mean, sure, some code is shorter, but the longer code we had before worked just fine, or was there some limitation I am not getting?

Instant represents a single point of time in the UTC time zone, while OffsetDateTime stores a zone offset. As such, I felt that storing an Instant would be better as it avoids having to convert between different time zones in order to obtain it, and a local date/time value can be obtained via their factory methods.

Copy link
Copy Markdown
Member

@Stypox Stypox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Generally this looks good to me, except for a few nitpicks

Comment thread extractor/src/main/java/org/schabi/newpipe/extractor/utils/ExtractorHelper.java Outdated
# Conflicts:
#	extractor/src/main/java/org/schabi/newpipe/extractor/localization/DateWrapper.java
Isira-Seneviratne and others added 2 commits October 8, 2025 07:27
@Isira-Seneviratne Isira-Seneviratne force-pushed the Refactor-date-parsing branch 2 times, most recently from 34c495a to b3fd6b3 Compare October 10, 2025 03:05
@Isira-Seneviratne Isira-Seneviratne merged commit 39b9482 into TeamNewPipe:dev Oct 10, 2025
4 checks passed
@TobiGr
Copy link
Copy Markdown
Contributor

TobiGr commented Oct 10, 2025

Please add this PR to the release notes and list the breaking changes in the "Breaking" section.

@Stypox

This comment was marked as duplicate.

@Stypox
Copy link
Copy Markdown
Member

Stypox commented Nov 26, 2025

@Isira-Seneviratne When running mock tests locally on my PC (in a CET timezone) I get the following failure which doesn't happen with the CI:

Multiple Failures (1 failure)
	org.opentest4j.AssertionFailedError: expected: <2026-03-15 13:12> but was: <2026-03-15 14:12>
org.opentest4j.MultipleFailuresError: Multiple Failures (1 failure)
	org.opentest4j.AssertionFailedError: expected: <2026-03-15 13:12> but was: <2026-03-15 14:12>
	at org.junit.jupiter.api.AssertAll.assertAll(AssertAll.java:80)
	at org.junit.jupiter.api.AssertAll.assertAll(AssertAll.java:44)
	at org.junit.jupiter.api.AssertAll.assertAll(AssertAll.java:38)
	at org.junit.jupiter.api.Assertions.assertAll(Assertions.java:2944)
	at org.schabi.newpipe.extractor.services.youtube.YoutubeStreamInfoItemTest.videoRendererPremiere(YoutubeStreamInfoItemTest.java:33)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	Suppressed: org.opentest4j.AssertionFailedError: expected: <2026-03-15 13:12> but was: <2026-03-15 14:12>
		at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
		at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
		at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
		at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
		at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:177)
		at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1145)
		at org.schabi.newpipe.extractor.services.youtube.YoutubeStreamInfoItemTest.lambda$videoRendererPremiere$9(YoutubeStreamInfoItemTest.java:43)
		at org.junit.jupiter.api.AssertAll.lambda$assertAll$0(AssertAll.java:68)
		at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
		at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
		at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
		at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
		at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
		at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
		at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
		at org.junit.jupiter.api.AssertAll.assertAll(AssertAll.java:77)
		... 7 more

I am specifically running all tests with mocks on commit 724cc46 from #1409

@Isira-Seneviratne
Copy link
Copy Markdown
Member Author

@Isira-Seneviratne When running mock tests locally on my PC (in a CET timezone) I get the following failure which doesn't happen with the CI:

Multiple Failures (1 failure)
	org.opentest4j.AssertionFailedError: expected: <2026-03-15 13:12> but was: <2026-03-15 14:12>
org.opentest4j.MultipleFailuresError: Multiple Failures (1 failure)
	org.opentest4j.AssertionFailedError: expected: <2026-03-15 13:12> but was: <2026-03-15 14:12>
	at org.junit.jupiter.api.AssertAll.assertAll(AssertAll.java:80)
	at org.junit.jupiter.api.AssertAll.assertAll(AssertAll.java:44)
	at org.junit.jupiter.api.AssertAll.assertAll(AssertAll.java:38)
	at org.junit.jupiter.api.Assertions.assertAll(Assertions.java:2944)
	at org.schabi.newpipe.extractor.services.youtube.YoutubeStreamInfoItemTest.videoRendererPremiere(YoutubeStreamInfoItemTest.java:33)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	Suppressed: org.opentest4j.AssertionFailedError: expected: <2026-03-15 13:12> but was: <2026-03-15 14:12>
		at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
		at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
		at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
		at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
		at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:177)
		at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1145)
		at org.schabi.newpipe.extractor.services.youtube.YoutubeStreamInfoItemTest.lambda$videoRendererPremiere$9(YoutubeStreamInfoItemTest.java:43)
		at org.junit.jupiter.api.AssertAll.lambda$assertAll$0(AssertAll.java:68)
		at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
		at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
		at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
		at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
		at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
		at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
		at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
		at org.junit.jupiter.api.AssertAll.assertAll(AssertAll.java:77)
		... 7 more

I am specifically running all tests with mocks on commit 724cc46 from #1409

Thanks, I'll take a look at it.

@Isira-Seneviratne
Copy link
Copy Markdown
Member Author

I fixed the issue here: #1411

@Stypox
Copy link
Copy Markdown
Member

Stypox commented Nov 26, 2025

Thank you!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants