diff --git a/app/graphql/resolvers/concerns/resolves_merge_requests.rb b/app/graphql/resolvers/concerns/resolves_merge_requests.rb
index 54ffba6073ca43fb3cc7f6e59108e292af5a8e25..c748666b6dd6d39c432215d380e26f3f68b83ccc 100644
--- a/app/graphql/resolvers/concerns/resolves_merge_requests.rb
+++ b/app/graphql/resolvers/concerns/resolves_merge_requests.rb
@@ -63,6 +63,8 @@ def preloads
       diff_stats_summary: [:metrics],
       approved_by: [:approved_by_users],
       merge_after: [:merge_schedule],
+      mergeable: [:merge_schedule],
+      detailed_merge_status: [:merge_schedule],
       milestone: [:milestone],
       security_auto_fix: [:author],
       head_pipeline: [:merge_request_diff, { head_pipeline: [:merge_request] }],
diff --git a/app/graphql/types/merge_requests/detailed_merge_status_enum.rb b/app/graphql/types/merge_requests/detailed_merge_status_enum.rb
index ce934458aec8423d8f5f9f1592ba4e474e264fa9..1dfc21f833444fa580d419e4e7d75c2b708245fa 100644
--- a/app/graphql/types/merge_requests/detailed_merge_status_enum.rb
+++ b/app/graphql/types/merge_requests/detailed_merge_status_enum.rb
@@ -66,6 +66,9 @@ class DetailedMergeStatusEnum < BaseEnum
       value 'SECURITY_POLICIES_EVALUATING',
         value: :security_policy_evaluation,
         description: 'All security policies must be evaluated.'
+      value 'MERGE_TIME',
+        value: :merge_time,
+        description: 'Merge request may not be merged until after the specified time.'
     end
   end
 end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 699af3ec43cd2528e1a40321b679ab80acc458e6..e3ac518209fc63c80d6e7442d03a960055ddadf2 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1297,7 +1297,8 @@ def skipped_mergeable_checks(options = {})
       skip_locked_paths_check: merge_when_checks_pass_strat,
       skip_jira_check: merge_when_checks_pass_strat,
       skip_locked_lfs_files_check: merge_when_checks_pass_strat,
-      skip_security_policy_check: merge_when_checks_pass_strat
+      skip_security_policy_check: merge_when_checks_pass_strat,
+      skip_merge_time_check: merge_when_checks_pass_strat
     }
   end
 
@@ -1325,6 +1326,7 @@ def self.mergeable_state_checks
     #
     [
       ::MergeRequests::Mergeability::CheckOpenStatusService,
+      ::MergeRequests::Mergeability::CheckMergeTimeService,
       ::MergeRequests::Mergeability::CheckDraftStatusService,
       ::MergeRequests::Mergeability::CheckCommitsStatusService,
       ::MergeRequests::Mergeability::CheckDiscussionsStatusService,
diff --git a/app/services/merge_requests/mergeability/check_merge_time_service.rb b/app/services/merge_requests/mergeability/check_merge_time_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cfdd60044247e56a17464ca228140572b3b337f4
--- /dev/null
+++ b/app/services/merge_requests/mergeability/check_merge_time_service.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module MergeRequests
+  module Mergeability
+    class CheckMergeTimeService < CheckBaseService
+      identifier :merge_time
+      description 'Checks whether the merge is blocked due to a scheduled merge time'
+
+      def execute
+        merge_after = merge_request.merge_schedule&.merge_after
+        return inactive if merge_after.nil?
+
+        if merge_after.future?
+          failure
+        else
+          success
+        end
+      end
+
+      def skip?
+        params[:skip_merge_time_check].present?
+      end
+
+      def cacheable?
+        false
+      end
+    end
+  end
+end
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index a67904baaed96550a755471dd58395b75335a4ec..39f3b40c5f66bdbf40d4d7ea123d7d4e0b96d28a 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -36982,6 +36982,7 @@ Detailed representation of whether a GitLab merge request can be merged.
 | <a id="detailedmergestatuslocked_lfs_files"></a>`LOCKED_LFS_FILES` | Merge request includes locked LFS files. |
 | <a id="detailedmergestatuslocked_paths"></a>`LOCKED_PATHS` | Merge request includes locked paths. |
 | <a id="detailedmergestatusmergeable"></a>`MERGEABLE` | Branch can be merged. |
+| <a id="detailedmergestatusmerge_time"></a>`MERGE_TIME` | Merge request may not be merged until after the specified time. |
 | <a id="detailedmergestatusneed_rebase"></a>`NEED_REBASE` | Merge request needs to be rebased. |
 | <a id="detailedmergestatusnot_approved"></a>`NOT_APPROVED` | Merge request must be approved before merging. |
 | <a id="detailedmergestatusnot_open"></a>`NOT_OPEN` | Merge request must be open before merging. |
@@ -37806,6 +37807,7 @@ Representation of mergeability check identifier.
 | <a id="mergeabilitycheckidentifierlocked_lfs_files"></a>`LOCKED_LFS_FILES` | Checks whether the merge request contains locked LFS files that are locked by users other than the merge request author. |
 | <a id="mergeabilitycheckidentifierlocked_paths"></a>`LOCKED_PATHS` | Checks whether the merge request contains locked paths. |
 | <a id="mergeabilitycheckidentifiermerge_request_blocked"></a>`MERGE_REQUEST_BLOCKED` | Checks whether the merge request is blocked. |
+| <a id="mergeabilitycheckidentifiermerge_time"></a>`MERGE_TIME` | Checks whether the merge is blocked due to a scheduled merge time. |
 | <a id="mergeabilitycheckidentifierneed_rebase"></a>`NEED_REBASE` | Checks whether the merge request needs to be rebased. |
 | <a id="mergeabilitycheckidentifiernot_approved"></a>`NOT_APPROVED` | Checks whether the merge request is approved. |
 | <a id="mergeabilitycheckidentifiernot_open"></a>`NOT_OPEN` | Checks whether the merge request is open. |
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 515da356e4d182261c07198d47778beea1a37a5c..f83442301180b23d9d6e3baf4ff1da55bd895083 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -909,6 +909,7 @@ Use `detailed_merge_status` instead of `merge_status` to account for all potenti
     [Require associated Jira issue for merge requests to be merged](../integration/jira/issues.md#require-associated-jira-issue-for-merge-requests-to-be-merged).
   - `mergeable`: The branch can merge cleanly into the target branch.
   - `merge_request_blocked`: Blocked by another merge request.
+  - `merge_time`: May not be merged until after the specified time.
   - `need_rebase`: The merge request must be rebased.
   - `not_approved`: Approval is required before merge.
   - `not_open`: The merge request must be open before merge.
diff --git a/ee/app/graphql/resolvers/concerns/ee/resolves_merge_requests.rb b/ee/app/graphql/resolvers/concerns/ee/resolves_merge_requests.rb
index 3f8abaa64c53a4859d4feab146ec91e7be077531..2391c1a95a0afbe76e335a81592c1e5b73ca9b04 100644
--- a/ee/app/graphql/resolvers/concerns/ee/resolves_merge_requests.rb
+++ b/ee/app/graphql/resolvers/concerns/ee/resolves_merge_requests.rb
@@ -8,14 +8,14 @@ module ResolvesMergeRequests
 
     def preloads
       super.tap do |h|
-        h[:mergeable] = [
+        h[:mergeable] += [
           *approved_mergeability_check_preloads,
           *blocked_by_other_mrs_mergeability_check_preloads,
           *commits_status_mergeability_check_preloads,
           *security_policy_evaluation_check_preloads
         ]
 
-        h[:detailed_merge_status] = [
+        h[:detailed_merge_status] += [
           *approved_mergeability_check_preloads,
           *blocked_by_other_mrs_mergeability_check_preloads,
           *commits_status_mergeability_check_preloads,
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 2fab1f07887caf431e16ff6a095a8b7676fbfde4..ce5d3f788a6201663016683a70d95ff8e07343ed 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -3945,7 +3945,8 @@ def set_compare(merge_request)
           is_expected.to include(skip_approved_check: skip_checks, skip_draft_check: skip_checks,
             skip_blocked_check: skip_checks, skip_discussions_check: skip_checks,
             skip_external_status_check: skip_checks, skip_requested_changes_check: skip_checks,
-            skip_jira_check: skip_checks, skip_security_policy_check: skip_checks)
+            skip_jira_check: skip_checks, skip_security_policy_check: skip_checks,
+            skip_merge_time_check: skip_checks)
         end
       end
     end
diff --git a/spec/services/merge_requests/mergeability/check_merge_time_service_spec.rb b/spec/services/merge_requests/mergeability/check_merge_time_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ba931501a3e082632ae7824f6e836f9b25c5f0ef
--- /dev/null
+++ b/spec/services/merge_requests/mergeability/check_merge_time_service_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe MergeRequests::Mergeability::CheckMergeTimeService, feature_category: :code_review_workflow do
+  subject(:check_merge_time) { described_class.new(merge_request: merge_request, params: params) }
+
+  let_it_be(:merge_request) { build(:merge_request) }
+
+  let(:params) { { skip_merge_time_check: skip_check } }
+  let(:skip_check) { false }
+
+  it_behaves_like 'mergeability check service', :merge_time,
+    'Checks whether the merge is blocked due to a scheduled merge time'
+
+  describe '#execute' do
+    let(:result) { check_merge_time.execute }
+
+    before do
+      merge_request.merge_schedule = build(
+        :merge_request_merge_schedule,
+        merge_request: merge_request,
+        merge_after: merge_after
+      )
+    end
+
+    context 'when merge_after is not set' do
+      let(:merge_after) { nil }
+
+      it 'returns a check result with status inactive' do
+        expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::INACTIVE_STATUS
+        expect(result.payload[:identifier]).to eq(:merge_time)
+      end
+    end
+
+    context 'when merge_after is in the future' do
+      let(:merge_after) { 1.minute.from_now }
+
+      it 'returns a check result with status failed' do
+        expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::FAILED_STATUS
+        expect(result.payload[:identifier]).to eq(:merge_time)
+      end
+    end
+
+    context 'when merge_after is in the past' do
+      let(:merge_after) { 1.minute.ago }
+
+      it 'returns a check result with status success' do
+        expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::SUCCESS_STATUS
+        expect(result.payload[:identifier]).to eq(:merge_time)
+      end
+    end
+  end
+
+  describe '#skip?' do
+    context 'when skip check param is true' do
+      let(:skip_check) { true }
+
+      it 'returns true' do
+        expect(check_merge_time.skip?).to eq true
+      end
+    end
+
+    context 'when skip check param is false' do
+      let(:skip_check) { false }
+
+      it 'returns false' do
+        expect(check_merge_time.skip?).to eq false
+      end
+    end
+  end
+
+  describe '#cacheable?' do
+    it 'returns false' do
+      expect(check_merge_time.cacheable?).to eq false
+    end
+  end
+end