diff --git a/dh_installchangelogs b/dh_installchangelogs
index 2f71833a4dfec70d7ad3a8c1acb1a1265c42a11b..3cb08cd8fa1c946cb7cdb73c58b2d56cb461ea55 100755
--- a/dh_installchangelogs
+++ b/dh_installchangelogs
@@ -9,12 +9,13 @@ dh_installchangelogs - install changelogs into package build directories
 use strict;
 use warnings;
 use Debian::Debhelper::Dh_Lib;
+use Time::Piece;
 
 our $VERSION = DH_BUILTIN_VERSION;
 
 =head1 SYNOPSIS
 
-B<dh_installchangelogs> [S<I<debhelper options>>] [B<-k>] [B<-X>I<item>] [I<upstream>]
+B<dh_installchangelogs> [S<I<debhelper options>>] [B<-k>] [B<-X>I<item>] [B<--no-trim>] [I<upstream>]
 
 =head1 DESCRIPTION
 
@@ -44,6 +45,10 @@ can be specified as a second parameter. When no plain text variant is
 specified, a short F<usr/share/doc/package/changelog> is generated,
 pointing readers at the html changelog file.
 
+B<debchange>-style Debian changelogs are trimmed to include only
+entries more recent than the release date of I<oldstable>.
+This behavior can be disabled by passing the B<--no-trim> option.
+
 =head1 FILES
 
 =over 4
@@ -87,6 +92,11 @@ filename from being installed.
 
 Note that directory name of the changelog is also part of the match.
 
+=item B<--no-trim>
+
+Install the full changelog, not its trimmed version that includes only
+recent entries.
+
 =item I<upstream>
 
 Install this file as the upstream changelog.
@@ -95,63 +105,17 @@ Install this file as the upstream changelog.
 
 =cut
 
-# For binNMUs the first changelog entry is written into an extra file to
-# keep the packages coinstallable.
-sub install_binNMU_changelog {
-	my ($package, $input_fn, $changelog_name)=@_;
-
-	open (my $input, "<", $input_fn);
-	my $line=<$input>;
-	if (defined $line && $line =~ /\A\S.*;.*\bbinary-only=yes/) {
-		my $mask=umask 0022;
-
-		my @stat=stat $input_fn or error("could not stat $input_fn: $!");
-		my $tmp=tmpdir($package);
-		my $output_fn="$tmp/usr/share/doc/$package/$changelog_name";
-		open my $output, ">", $output_fn
-			or error("could not open $output_fn for writing: $!");
-		my $arch=package_binary_arch($package);
-		my $output_fn_binary="$output_fn.$arch";
-		open my $output_binary, ">", $output_fn_binary
-			or error("could not open $output_fn_binary for writing: $!");
-
-		do {
-			print {$output_binary} $line
-				or error("Could not write to $output_fn_binary: $!");
-		} while(defined($line=<$input>) && $line !~ /\A\S/);
-		close $output_binary or error("Couldn't close $output_fn_binary: $!");
-		utime $stat[8], $stat[9], $output_fn_binary;
-
-		do {
-			print {$output} $line
-				or error("Could not write to $output_fn: $!");
-		} while(defined($line=<$input>));
-
-		close $input or error("Couldn't close $input_fn: $!");
-		close $output or error("Couldn't close $output_fn: $!");
-		utime $stat[8], $stat[9], $output_fn;
-
-		if (should_use_root()) {
-			chown(0, 0, $output_fn, $output_fn_binary) or error("chown: $!");
-		}
-
-		umask $mask;
-
-		return 1;
-	}
-	else {
-		close $input;
-		return 0;
-	}
-}
-
 init(options => {
 	'keep|k'  => \$dh{K_FLAG},
+	'no-trim' => \$dh{NO_TRIM},
 });
 
 my $news_name="NEWS.Debian";
 my $changelog_name="changelog.Debian";
 
+use constant CUTOFF_DATE_STR => "2019-07-06"; # oldstable = Debian 10 Buster
+use constant CUTOFF_DATE => Time::Piece->strptime(CUTOFF_DATE_STR, "%Y-%m-%d");
+
 my $explicit_changelog = @ARGV ? 1 : 0;
 my $default_upstream = $ARGV[0];
 my $default_upstream_text=$default_upstream;
@@ -192,6 +156,106 @@ sub find_changelog {
 	return;
 }
 
+sub install_debian_changelog {
+	my ($changelog, $package, $arch, $tmp) = @_;
+
+	if ($dh{NO_TRIM}) {
+		# Install the whole changelog.
+		install_file($changelog, "$tmp/usr/share/doc/$package/$changelog_name");
+		return;
+	}
+
+	local $ENV{LC_ALL} = "C.UTF-8";
+
+	my $changelog_trimmed = "$changelog.trimmed.debhelper";
+	my $changelog_binnmu = "$changelog.binnmu.debhelper";
+
+	open(my $log1, "<", $changelog) or error("Cannot open($changelog): $!");
+	open(my $log2, ">", $changelog_trimmed) or error("Cannot open($changelog_trimmed): $!");
+
+	my $error_in_changelog = 0;
+	my $is_binnmu = 0;
+	my $entry = "";
+	my $entry_num = 0;
+	while (my $line=<$log1>) {
+		$entry .= $line;
+
+		# Identify binNUM packages by binary-only=yes in the first line of the changelog.
+		if (($. == 1) && ($line =~ /\A\S.*;.*\bbinary-only=yes/)) {
+			$is_binnmu = 1;
+		}
+
+		# Get out of binNMU mode once we are in the second entry (and throw away one empty line).
+		if ($is_binnmu && ($entry_num eq 1)) {
+			$is_binnmu = 0;
+			$entry_num = 0;
+			$entry = "";
+			next;
+		}
+
+		if ($line =~ /^\s*--\s+.*?\s+<[^>]*>\s+(?<timestamp>.*)$/) {
+			if ($is_binnmu && ($entry_num eq 0)) {
+				# For binNMUs the first changelog entry is written into an extra file to
+				# keep the packages coinstallable.
+				open(my $log_binnum, ">", $changelog_binnmu) or error("Cannot open($changelog_binnmu): $!");
+				print($log_binnum $entry) or error("Cannot write($changelog_binnmu): $!");
+				close($log_binnum) or error("Cannot close($changelog_binnmu): $!");
+
+				# Continue processing the rest of the changelog.
+				$entry = "";
+				$entry_num++;
+				next;
+			}
+
+			my $timestamp = $+{timestamp};
+			$timestamp =~ s/^[A-Za-z]+, +//;
+
+			my $entry_time;
+			eval { $entry_time = Time::Piece->strptime($timestamp, '%d %b %Y %T %z') };
+			if (! defined $entry_time) {
+				$error_in_changelog = 1;
+				warning("Could not parse timestamp '$timestamp'. $changelog will not be trimmed.");
+				truncate($log2, 0) or error("Cannot truncate($changelog_trimmed): $!");
+				last;
+			}
+
+			# Stop processing the changelog if we reached the cut-off date and at least one entry has been added.
+			if (($entry_time < CUTOFF_DATE) && ($entry_num >= 1)) {
+				last;
+			}
+
+			# Append entry to trimmed changelog.
+			print($log2 $entry) or error("Cannot write($changelog_trimmed): $!");
+			$entry = "";
+			$entry_num++;
+		}
+	}
+	# If the whole changelog has not been read, then it has been trimmed.
+	my $has_been_trimmed = !eof($log1);
+
+	close($log1) or error("Cannot close($changelog): $!");
+	close($log2) or error("Cannot close($changelog_trimmed): $!");
+
+	if ($error_in_changelog) {
+		# If the changelog could not be trimmed, fall back to the full changelog.
+		warning("$changelog could not be trimmed. The full changelog will be installed.");
+		$changelog_trimmed = $changelog;
+	} elsif ($has_been_trimmed) {
+		# Otherwise add a comment stating that this changelog has been trimmed.
+		my $note = "\n";
+		$note .= "# Older entries have been removed from this changelog.\n";
+		$note .= "# To read the complete changelog use `apt changelog $package`.\n";
+		open(my $log2, ">>", $changelog_trimmed) or error("Cannot open($changelog_trimmed): $!");
+		print($log2 $note) or error("Cannot write($changelog_trimmed): $!");
+		close($log2) or error("Cannot close($changelog_trimmed): $!");
+	}
+
+	install_file($changelog_trimmed, "$tmp/usr/share/doc/$package/$changelog_name");
+	if (-s $changelog_binnmu) {
+		install_file($changelog_binnmu, "$tmp/usr/share/doc/$package/$changelog_name.$arch");
+	}
+}
+
 on_pkgs_in_parallel {
 	foreach my $package (@_) {
 		next if is_udeb($package);
@@ -249,10 +313,8 @@ on_pkgs_in_parallel {
 		install_dir("$tmp/usr/share/doc/$package");
 
 		if (! $dh{NO_ACT}) {
-			if (! install_binNMU_changelog($package, $changelog, $changelog_name)) {
-				install_file($changelog,
-							 "$tmp/usr/share/doc/$package/$changelog_name");
-			}
+			my $arch = package_binary_arch($package);
+			install_debian_changelog($changelog, $package, $arch, $tmp);
 		}
 
 		if (-e $news) {
diff --git a/t/dh_installchangelogs/debian/control b/t/dh_installchangelogs/debian/control
new file mode 100644
index 0000000000000000000000000000000000000000..4f4238ebadc0de99525774b33d723dff6d92ae98
--- /dev/null
+++ b/t/dh_installchangelogs/debian/control
@@ -0,0 +1,10 @@
+Source: foo
+Section: misc
+Priority: optional
+Maintainer: Test <testing@nowhere>
+Standards-Version: 3.9.8
+
+Package: foo
+Architecture: all
+Description: package foo
+ Package foo
diff --git a/t/dh_installchangelogs/dh_installchangelogs.t b/t/dh_installchangelogs/dh_installchangelogs.t
new file mode 100755
index 0000000000000000000000000000000000000000..09560575dd729c198aeef67316446ece17e9b5b3
--- /dev/null
+++ b/t/dh_installchangelogs/dh_installchangelogs.t
@@ -0,0 +1,208 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Test::More;
+use Time::Piece;
+use Time::Seconds qw(ONE_MONTH ONE_YEAR);
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+
+use constant TEST_DIR => dirname($0);
+our @TEST_DH_EXTRA_TEMPLATE_FILES = (qw(
+    debian/changelog
+    debian/control
+));
+
+use constant CUTOFF_DATE_STR => "2019-07-06"; # oldstable = Debian 10 Buster
+use constant CUTOFF_DATE => Time::Piece->strptime(CUTOFF_DATE_STR, "%Y-%m-%d");
+
+sub install_changelog {
+	my ($latest_offset_years, $num_years, $is_binnmu) = @_;
+	$is_binnmu //= 0;
+
+	my $entry_date_first = CUTOFF_DATE->add_years($latest_offset_years);
+	my $entry_date_stop = $entry_date_first->add_years(-$num_years);
+
+	my $changelog = "${\TEST_DIR}/debian/changelog";
+
+	open(my $fd, ">", $changelog) or error("open($changelog): $!");
+
+	if ($is_binnmu) {
+		my $nmu_date = $entry_date_first->add_months(-1);
+		my $nmu_entry = entry_text($nmu_date, 1);
+		print($fd $nmu_entry);
+	}
+
+	my $entry_date = $entry_date_first;
+	while ($entry_date > $entry_date_stop) {
+		my $entry = entry_text($entry_date, 0);
+		print($fd $entry);
+
+		$entry_date = $entry_date->add_months(-3);
+	}
+	close($fd);
+}
+
+sub entry_text {
+	my ($entry_date, $is_binnmu) = @_;
+	my $entry_date_str = $entry_date->strftime("%a, %d %b %Y %T %z");
+	my $ver = $entry_date->year . "." . $entry_date->mon . "-1";
+	my $binnmu_text = "";
+
+	if ($is_binnmu) {
+		$binnmu_text = " binary-only=yes";
+		$ver .= "+b1";
+	}
+
+	my $entry = "";
+	$entry .= "foo ($ver) unstable; urgency=low$binnmu_text\n\n";
+	$entry .= "  * New release.\n\n";
+	$entry .= " -- Test <testing\@nowhere>  $entry_date_str\n\n";
+
+	return $entry;
+}
+
+sub changelog_lines_pkg {
+	return changelog_lines("debian/changelog");
+}
+sub changelog_lines_installed {
+	return changelog_lines("debian/foo/usr/share/doc/foo/changelog.Debian");
+}
+sub changelog_lines_binnmu {
+	return changelog_lines("debian/foo/usr/share/doc/foo/changelog.Debian.all");
+}
+sub changelog_lines {
+	my ($changelog) = @_;
+	open(my $fd, $changelog) or error("open($changelog): $!");
+	my @lines = @{readlines($fd)};
+	@lines = grep(!/^$/, @lines);
+	return @lines;
+}
+
+sub dates_in_lines {
+	my @lines = @_;
+	my @lines_dates = grep(/^ -- /, @lines);
+	@lines_dates = map { (my $l = $_) =~ s/^\s*--\s+.*?\s+<[^>]*>\s+[A-Za-z]+, +//; $l }  @lines_dates;
+	@lines_dates = map { Time::Piece->strptime($_, "%d %b %Y %T %z") }  @lines_dates;
+	return @lines_dates;
+}
+
+plan(tests => 6);
+
+# Test changelog with only recent entries (< oldstable)
+my $years_after_cutoff = 2;
+my $years_of_changelog = 2;
+install_changelog($years_after_cutoff, $years_of_changelog);
+each_compat_subtest {
+	my @lines_orig = changelog_lines_pkg();
+	ok(run_dh_tool("dh_installchangelogs"));
+	my @lines = changelog_lines_installed();
+	my @comments = grep(/^#/, @lines);
+
+	is(@lines, @lines_orig);
+	is(@comments, 0);
+};
+
+# Test changelog with both recent and old entries
+$years_after_cutoff = 1;
+$years_of_changelog = 4;
+install_changelog($years_after_cutoff, $years_of_changelog);
+each_compat_subtest {
+	my @lines_orig = changelog_lines_pkg();
+	ok(run_dh_tool("dh_installchangelogs"));
+	my @lines = changelog_lines_installed();
+	my @entries = dates_in_lines(@lines);
+	my @entries_old = grep { $_ < CUTOFF_DATE } @entries;
+	my @comments = grep(/^#/, @lines);
+
+	cmp_ok(@lines, "<", @lines_orig);
+	cmp_ok(@entries, ">", 1);
+	is(@entries_old, 0);
+	cmp_ok(@comments, ">=", 1);
+};
+
+# Test changelog with only old entries
+$years_after_cutoff = -1;
+$years_of_changelog = 2;
+install_changelog($years_after_cutoff, $years_of_changelog);
+each_compat_subtest {
+	my @lines_orig = changelog_lines_pkg();
+	ok(run_dh_tool("dh_installchangelogs"));
+	my @lines = changelog_lines_installed();
+	my @entries = dates_in_lines(@lines);
+	my @entries_old = grep { $_ < CUTOFF_DATE } @entries;
+	my @comments = grep(/^#/, @lines);
+
+	cmp_ok(@lines, "<", @lines_orig);
+	is(@entries, 1);
+	is(@entries_old, 1);
+	cmp_ok(@comments, ">=", 1);
+};
+
+# Test changelog with only recent entries (< oldstable) + binNUM
+$years_after_cutoff = 2;
+$years_of_changelog = 2;
+install_changelog($years_after_cutoff, $years_of_changelog, 1);
+each_compat_subtest {
+	my @lines_orig = changelog_lines_pkg();
+	my @entries_orig = dates_in_lines(@lines_orig);
+	ok(run_dh_tool("dh_installchangelogs"));
+	my @lines = changelog_lines_installed();
+	my @entries = dates_in_lines(@lines);
+	my @entries_nmu = dates_in_lines(changelog_lines_binnmu());
+	my @comments = grep(/^#/, @lines);
+
+	is(@entries, @entries_orig-1);
+	is($entries[0], $entries_orig[1]);
+	is(@comments, 0);
+
+	is(@entries_nmu, 1);
+};
+
+# Test changelog with both recent and old entries + binNMU
+$years_after_cutoff = 1;
+$years_of_changelog = 4;
+install_changelog($years_after_cutoff, $years_of_changelog, 1);
+each_compat_subtest {
+	my @lines_orig = changelog_lines_pkg();
+	my @entries_orig = dates_in_lines(@lines_orig);
+	ok(run_dh_tool("dh_installchangelogs"));
+	my @lines = changelog_lines_installed();
+	my @entries = dates_in_lines(@lines);
+	my @entries_old = grep { $_ < CUTOFF_DATE } @entries;
+	my @entries_nmu = dates_in_lines(changelog_lines_binnmu());
+	my @comments = grep(/^#/, @lines);
+
+	cmp_ok(@entries, "<", @entries_orig-1);
+	is($entries[0], $entries_orig[1]);
+	is(@entries_old, 0);
+	cmp_ok(@comments, ">=", 1);
+
+	is(@entries_nmu, 1);
+};
+
+# Test changelog with only old entries + binNMU
+$years_after_cutoff = -1;
+$years_of_changelog = 2;
+install_changelog($years_after_cutoff, $years_of_changelog, 1);
+each_compat_subtest {
+	my @lines_orig = changelog_lines_pkg();
+	my @entries_orig = dates_in_lines(@lines_orig);
+	ok(run_dh_tool("dh_installchangelogs"));
+	my @lines = changelog_lines_installed();
+	my @entries = dates_in_lines(@lines);
+	my @entries_old = grep { $_ < CUTOFF_DATE } @entries;
+	my @entries_nmu = dates_in_lines(changelog_lines_binnmu());
+	my @comments = grep(/^#/, @lines);
+
+	is(@entries, 1);
+	is($entries[0], $entries_orig[1]);
+	is(@entries_old, 1);
+	cmp_ok(@comments, ">=", 1);
+
+	is(@entries_nmu, 1);
+};
+
+unlink("${\TEST_DIR}/debian/changelog");