diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp
index 54798914c..a2e26ed35 100644
--- a/Telegram/SourceFiles/export/data/export_data_types.cpp
+++ b/Telegram/SourceFiles/export/data/export_data_types.cpp
@@ -1569,6 +1569,44 @@ MessagesSlice ParseMessagesSlice(
 	return result;
 }
 
+TimeId SingleMessageDate(const MTPmessages_Messages &data) {
+	return data.match([&](const MTPDmessages_messagesNotModified &data) {
+		return 0;
+	}, [&](const auto &data) {
+		const auto &list = data.vmessages.v;
+		if (list.isEmpty()) {
+			return 0;
+		}
+		return list[0].match([](const MTPDmessageEmpty &data) {
+			return 0;
+		}, [](const auto &data) {
+			return data.vdate.v;
+		});
+	});
+}
+
+bool SingleMessageBefore(
+		const MTPmessages_Messages &data,
+		TimeId date) {
+	const auto single = SingleMessageDate(data);
+	return (single > 0 && single < date);
+}
+
+bool SingleMessageAfter(
+		const MTPmessages_Messages &data,
+		TimeId date) {
+	const auto single = SingleMessageDate(data);
+	return (single > 0 && single > date);
+}
+
+bool SkipMessageByDate(const Message &message, const Settings &settings) {
+	const auto goodFrom = (settings.singlePeerFrom <= 0)
+		|| (settings.singlePeerFrom <= message.date);
+	const auto goodTill = (settings.singlePeerTill <= 0)
+		|| (message.date < settings.singlePeerTill);
+	return !goodFrom || !goodTill;
+}
+
 Utf8String FormatPhoneNumber(const Utf8String &phoneNumber) {
 	return phoneNumber.isEmpty()
 		? Utf8String()
diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h
index fb6e03f84..f6605dac4 100644
--- a/Telegram/SourceFiles/export/data/export_data_types.h
+++ b/Telegram/SourceFiles/export/data/export_data_types.h
@@ -66,6 +66,7 @@ struct File {
 		Unavailable,
 		FileType,
 		FileSize,
+		DateLimits,
 	};
 	FileLocation location;
 	int size = 0;
@@ -568,6 +569,14 @@ MessagesSlice ParseMessagesSlice(
 	const MTPVector<MTPChat> &chats,
 	const QString &mediaFolder);
 
+bool SingleMessageBefore(
+	const MTPmessages_Messages &data,
+	TimeId date);
+bool SingleMessageAfter(
+	const MTPmessages_Messages &data,
+	TimeId date);
+bool SkipMessageByDate(const Message &message, const Settings &settings);
+
 Utf8String FormatPhoneNumber(const Utf8String &phoneNumber);
 Utf8String FormatDateTime(
 	TimeId date,
diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp
index dee4a0949..f183f3b86 100644
--- a/Telegram/SourceFiles/export/export_api_wrap.cpp
+++ b/Telegram/SourceFiles/export/export_api_wrap.cpp
@@ -931,7 +931,7 @@ void ApiWrap::requestMessagesCount(int localSplitIndex) {
 		Expects(_chatProcess != nullptr);
 
 		const auto count = result.match(
-		[](const MTPDmessages_messages &data) {
+			[](const MTPDmessages_messages &data) {
 			return data.vmessages.v.size();
 		}, [](const MTPDmessages_messagesSlice &data) {
 			return data.vcount.v;
@@ -944,15 +944,55 @@ void ApiWrap::requestMessagesCount(int localSplitIndex) {
 			error("Unexpected messagesNotModified received.");
 			return;
 		}
-		_chatProcess->info.messagesCountPerSplit[localSplitIndex] = count;
-		if (localSplitIndex + 1 < _chatProcess->info.splits.size()) {
-			requestMessagesCount(localSplitIndex + 1);
-		} else if (_chatProcess->start(_chatProcess->info)) {
-			requestMessagesSlice();
+		const auto skipSplit = !Data::SingleMessageAfter(
+			result,
+			_settings->singlePeerFrom);
+		if (skipSplit) {
+			// No messages from the requested range, skip this split.
+			messagesCountLoaded(localSplitIndex, 0);
+			return;
 		}
+		checkFirstMessageDate(localSplitIndex, count);
 	});
 }
 
+void ApiWrap::checkFirstMessageDate(int localSplitIndex, int count) {
+	Expects(_chatProcess != nullptr);
+	Expects(localSplitIndex < _chatProcess->info.splits.size());
+
+	if (_settings->singlePeerTill <= 0) {
+		messagesCountLoaded(localSplitIndex, count);
+		return;
+	}
+
+	// Request first message in this split to check if its' date < till.
+	requestChatMessages(
+		_chatProcess->info.splits[localSplitIndex],
+		1, // offset_id
+		-1, // add_offset
+		1, // limit
+		[=](const MTPmessages_Messages &result) {
+		Expects(_chatProcess != nullptr);
+
+		const auto skipSplit = !Data::SingleMessageBefore(
+			result,
+			_settings->singlePeerTill);
+		messagesCountLoaded(localSplitIndex, skipSplit ? 0 : count);
+	});
+}
+
+void ApiWrap::messagesCountLoaded(int localSplitIndex, int count) {
+	Expects(_chatProcess != nullptr);
+	Expects(localSplitIndex < _chatProcess->info.splits.size());
+
+	_chatProcess->info.messagesCountPerSplit[localSplitIndex] = count;
+	if (localSplitIndex + 1 < _chatProcess->info.splits.size()) {
+		requestMessagesCount(localSplitIndex + 1);
+	} else if (_chatProcess->start(_chatProcess->info)) {
+		requestMessagesSlice();
+	}
+}
+
 void ApiWrap::finishExport(FnMut<void()> done) {
 	const auto guard = gsl::finally([&] { _takeoutId = std::nullopt; });
 
@@ -1200,6 +1240,12 @@ void ApiWrap::appendChatsSlice(
 void ApiWrap::requestMessagesSlice() {
 	Expects(_chatProcess != nullptr);
 
+	const auto count = _chatProcess->info.messagesCountPerSplit[
+		_chatProcess->localSplitIndex];
+	if (!count) {
+		loadMessagesFiles({});
+		return;
+	}
 	requestChatMessages(
 		_chatProcess->info.splits[_chatProcess->localSplitIndex],
 		_chatProcess->largestIdPlusOne,
@@ -1308,6 +1354,10 @@ void ApiWrap::loadNextMessageFile() {
 	for (auto &list = _chatProcess->slice->list
 		; _chatProcess->fileIndex < list.size()
 		; ++_chatProcess->fileIndex) {
+		const auto &message = list[_chatProcess->fileIndex];
+		if (Data::SkipMessageByDate(message, *_settings)) {
+			continue;
+		}
 		const auto fileProgress = [=](FileProgress value) {
 			return loadMessageFileProgress(value);
 		};
@@ -1452,7 +1502,10 @@ bool ApiWrap::processFileLoad(
 	}) : Type(0);
 
 	const auto limit = _settings->media.sizeLimit;
-	if ((_settings->media.types & type) != type) {
+	if (message && Data::SkipMessageByDate(*message, *_settings)) {
+		file.skipReason = SkipReason::DateLimits;
+		return true;
+	} else if ((_settings->media.types & type) != type) {
 		file.skipReason = SkipReason::FileType;
 		return true;
 	} else if ((message ? message->file().size : file.size) >= limit) {
diff --git a/Telegram/SourceFiles/export/export_api_wrap.h b/Telegram/SourceFiles/export/export_api_wrap.h
index 991008746..c0f28d819 100644
--- a/Telegram/SourceFiles/export/export_api_wrap.h
+++ b/Telegram/SourceFiles/export/export_api_wrap.h
@@ -141,6 +141,8 @@ private:
 		int splitIndex);
 
 	void requestMessagesCount(int localSplitIndex);
+	void checkFirstMessageDate(int localSplitIndex, int count);
+	void messagesCountLoaded(int localSplitIndex, int count);
 	void requestMessagesSlice();
 	void requestChatMessages(
 		int splitIndex,
diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp
index 7cd6eb494..c5f2a7cfe 100644
--- a/Telegram/SourceFiles/export/output/export_output_html.cpp
+++ b/Telegram/SourceFiles/export/output/export_output_html.cpp
@@ -2279,6 +2279,9 @@ Result HtmlWriter::writeDialogSlice(const Data::MessagesSlice &data) {
 	auto saved = std::optional<MessageInfo>();
 	auto block = QByteArray();
 	for (const auto &message : data.list) {
+		if (Data::SkipMessageByDate(message, _settings)) {
+			continue;
+		}
 		const auto newIndex = (_messagesCount / kMessagesInFile);
 		if (oldIndex != newIndex) {
 			if (const auto result = _chat->writeBlock(block); !result) {
@@ -2328,7 +2331,7 @@ Result HtmlWriter::writeDialogSlice(const Data::MessagesSlice &data) {
 	if (saved) {
 		_lastMessageInfo = std::make_unique<MessageInfo>(*saved);
 	}
-	return _chat->writeBlock(block);
+	return block.isEmpty() ? Result::Success() : _chat->writeBlock(block);
 }
 
 Result HtmlWriter::writeEmptySinglePeer() {
@@ -2345,7 +2348,7 @@ Result HtmlWriter::writeEmptySinglePeer() {
 		--_dateMessageId,
 		_dialog,
 		_settings.path,
-		"Empty chat"));
+		"No exported messages"));
 }
 
 Result HtmlWriter::writeDialogEnd() {
diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp
index 02f70c901..03b487864 100644
--- a/Telegram/SourceFiles/export/output/export_output_json.cpp
+++ b/Telegram/SourceFiles/export/output/export_output_json.cpp
@@ -1010,13 +1010,16 @@ Result JsonWriter::writeDialogSlice(const Data::MessagesSlice &data) {
 
 	auto block = QByteArray();
 	for (const auto &message : data.list) {
+		if (Data::SkipMessageByDate(message, _settings)) {
+			continue;
+		}
 		block.append(prepareArrayItemStart() + SerializeMessage(
 			_context,
 			message,
 			data.peers,
 			_environment.internalLinksDomain));
 	}
-	return _output->writeBlock(block);
+	return block.isEmpty() ? Result::Success() : _output->writeBlock(block);
 }
 
 Result JsonWriter::writeDialogEnd() {
diff --git a/Telegram/SourceFiles/export/output/export_output_text.cpp b/Telegram/SourceFiles/export/output/export_output_text.cpp
index 784c8a9d9..c2a97d43e 100644
--- a/Telegram/SourceFiles/export/output/export_output_text.cpp
+++ b/Telegram/SourceFiles/export/output/export_output_text.cpp
@@ -830,14 +830,20 @@ Result TextWriter::writeDialogSlice(const Data::MessagesSlice &data) {
 	Expects(_chat != nullptr);
 	Expects(!data.list.empty());
 
-	_messagesCount += data.list.size();
 	auto list = std::vector<QByteArray>();
 	list.reserve(data.list.size());
 	for (const auto &message : data.list) {
+		if (Data::SkipMessageByDate(message, _settings)) {
+			continue;
+		}
 		list.push_back(SerializeMessage(
 			message,
 			data.peers,
 			_environment.internalLinksDomain));
+		++_messagesCount;
+	}
+	if (list.empty()) {
+		return Result::Success();
 	}
 	const auto full = _chat->empty()
 		? JoinList(kLineBreak, list)
diff --git a/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp b/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp
index be010aabe..74630a794 100644
--- a/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp
+++ b/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp
@@ -119,6 +119,9 @@ void ResolveSettings(Settings &settings) {
 	} else {
 		settings.forceSubPath = IsDefaultPath(settings.path);
 	}
+	if (!settings.onlySinglePeer()) {
+		settings.singlePeerFrom = settings.singlePeerTill = 0;
+	}
 }
 
 PanelController::PanelController(not_null<ControllerWrap*> process)