diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index 59f34f5cac..4538e8e538 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -39,6 +39,7 @@
 #include "access/nbtree.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
@@ -95,6 +96,8 @@ typedef struct GeneratePruningStepsContext
 {
 	int			next_step_id;
 	List	   *steps;
+	bool		forplanner;		/* true when generating steps to be used
+								 * during query planning */
 } GeneratePruningStepsContext;
 
 /* The result of performing one PartitionPruneStep */
@@ -117,7 +120,7 @@ static List *make_partitionedrel_pruneinfo(PlannerInfo *root,
 							  List *partitioned_rels, List *prunequal,
 							  Bitmapset **matchedsubplans);
 static List *gen_partprune_steps(RelOptInfo *rel, List *clauses,
-					bool *contradictory);
+					bool forplanner, bool *contradictory);
 static List *gen_partprune_steps_internal(GeneratePruningStepsContext *context,
 							 RelOptInfo *rel, List *clauses,
 							 bool *contradictory);
@@ -409,8 +412,13 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 												  targetpart->relids);
 		}
 
-		/* Convert pruning qual to pruning steps. */
-		pruning_steps = gen_partprune_steps(subpart, partprunequal,
+		/*
+		 * Convert pruning qual to pruning steps.  Since these steps will be
+		 * executed during execution, we can pass 'forplanner' as false to
+		 * allow steps to be generated that are unsafe for evaluation during
+		 * planning, e.g. evaluation of stable functions.
+		 */
+		pruning_steps = gen_partprune_steps(subpart, partprunequal, false,
 											&contradictory);
 
 		if (contradictory)
@@ -523,18 +531,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 /*
  * gen_partprune_steps
  *		Process 'clauses' (a rel's baserestrictinfo list of clauses) and return
- *		a list of "partition pruning steps"
+ *		a list of "partition pruning steps".
+ *
+ * 'forplanner' must be true when generating steps to be evaluated during
+ * query planning.
  *
  * If the clauses in the input list are contradictory or there is a
  * pseudo-constant "false", *contradictory is set to true upon return.
  */
 static List *
-gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory)
+gen_partprune_steps(RelOptInfo *rel, List *clauses, bool forplanner,
+					bool *contradictory)
 {
 	GeneratePruningStepsContext context;
 
 	context.next_step_id = 0;
 	context.steps = NIL;
+	context.forplanner = forplanner;
 
 	/* The clauses list may be modified below, so better make a copy. */
 	clauses = list_copy(clauses);
@@ -574,9 +587,11 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory)
 
 /*
  * prune_append_rel_partitions
- *		Returns indexes into rel->part_rels of the minimum set of child
- *		partitions which must be scanned to satisfy rel's baserestrictinfo
- *		quals.
+ *		Process rel's baserestrictinfo and make use of quals which can be
+ *		evaluated during query planning in order to determine the minimum set
+ *		of partitions which must be scanned to satisfy these quals.  Returns
+ *		the matching partitions in the form of a Bitmapset containing the
+ *		indexes into rel's part_rels array.
  *
  * Callers must ensure that 'rel' is a partitioned table.
  */
@@ -603,9 +618,12 @@ prune_append_rel_partitions(RelOptInfo *rel)
 
 	/*
 	 * Process clauses.  If the clauses are found to be contradictory, we can
-	 * return the empty set.
+	 * return the empty set.  Pass 'forplanner' as true to indicate to the
+	 * pruning code that we only want pruning steps that can be evaluated
+	 * during planning.
 	 */
-	pruning_steps = gen_partprune_steps(rel, clauses, &contradictory);
+	pruning_steps = gen_partprune_steps(rel, clauses, true,
+										&contradictory);
 	if (contradictory)
 		return NULL;
 
@@ -635,6 +653,9 @@ prune_append_rel_partitions(RelOptInfo *rel)
  * get_matching_partitions
  *		Determine partitions that survive partition pruning
  *
+ * Note: context->planstate must be set to a valid PlanState when the
+ * pruning_steps were generated with 'forplanner' = false.
+ *
  * Returns a Bitmapset of the RelOptInfo->part_rels indexes of the surviving
  * partitions.
  */
@@ -1646,18 +1667,48 @@ match_clause_to_partition_key(RelOptInfo *rel,
 
 		/*
 		 * Matched with this key.  Now check various properties of the clause
-		 * to see if it's sane to use it for pruning.  In most of these cases,
-		 * we can return UNSUPPORTED because the same failure would occur no
-		 * matter which partkey it's matched to.
+		 * to see if it's sane to use it for pruning.  This sometimes depends
+		 * on if we're generating pruning steps for evaluation in the planner
+		 * or in the executor.  The planner is unable to evaluate any stable
+		 * functions, for example.  In most of these cases, we can return
+		 * UNSUPPORTED because the same failure would occur no matter which
+		 * partkey it's matched to.
 		 */
 
-		/*
-		 * We can't prune using an expression with Vars.  (Report failure as
-		 * UNSUPPORTED, not NOMATCH: as in the no-commutator case above, we
-		 * now know there are Vars on both sides, so it's no good.)
-		 */
-		if (contain_var_clause((Node *) expr))
-			return PARTCLAUSE_UNSUPPORTED;
+		if (context->forplanner)
+		{
+			/*
+			 * When pruning in the planner, we only support pruning using
+			 * constants.  Immutable functions will have been folded to consts
+			 * already so we could be dealing with a Var or an unsupported
+			 * function call, to which we must reject.
+			 */
+			if (!IsA(expr, Const))
+				return PARTCLAUSE_UNSUPPORTED;
+
+			/*
+			 * We must reject comparing to consts if the comparison function
+			 * itself is not immutable.  (This is the case for most of the
+			 * built-in timestamptz operators.)
+			 */
+			if (op_volatile(opno) != PROVOLATILE_IMMUTABLE)
+				return PARTCLAUSE_UNSUPPORTED;
+		}
+		else
+		{
+			/*
+			 * Otherwise, non-consts are supported, but we can't prune using
+			 * an expression with Vars.  (Report failure as UNSUPPORTED, not
+			 * NOMATCH: as in the no-commutator case above, we now know there
+			 * are Vars on both sides, so it's no good.)
+			 */
+			if (contain_var_clause((Node *) expr))
+				return PARTCLAUSE_UNSUPPORTED;
+
+			/* And we must reject anything containing a volatile function. */
+			if (contain_volatile_functions((Node *) expr))
+				return PARTCLAUSE_UNSUPPORTED;
+		}
 
 		/*
 		 * Only allow strict operators.  This will guarantee nulls are
@@ -1666,10 +1717,6 @@ match_clause_to_partition_key(RelOptInfo *rel,
 		if (!op_strict(opno))
 			return PARTCLAUSE_UNSUPPORTED;
 
-		/* We can't use any volatile expressions to prune partitions. */
-		if (contain_volatile_functions((Node *) expr))
-			return PARTCLAUSE_UNSUPPORTED;
-
 		/*
 		 * See if the operator is relevant to the partitioning opfamily.
 		 *
@@ -2961,15 +3008,18 @@ analyze_partkey_exprs(PartitionedRelPruneInfo *pinfo, List *steps,
 	{
 		PartitionPruneStepOp *step = (PartitionPruneStepOp *) lfirst(lc);
 		ListCell   *lc2;
+		ListCell   *lc3;
 		int			keyno;
 
 		if (!IsA(step, PartitionPruneStepOp))
 			continue;
 
 		keyno = 0;
-		foreach(lc2, step->exprs)
+		forboth(lc2, step->exprs, lc3, step->cmpfns)
 		{
 			Expr	   *expr = lfirst(lc2);
+			Oid			fnoid = lfirst_oid(lc3);
+
 
 			if (!IsA(expr, Const))
 			{
@@ -2992,6 +3042,18 @@ analyze_partkey_exprs(PartitionedRelPruneInfo *pinfo, List *steps,
 
 				doruntimeprune = true;
 			}
+
+			/*
+			 * Pruning done during planning won't have pruned using quals with
+			 * a non-immutable comparison functions, so we'll switch on
+			 * run-time pruning for these.
+			 */
+			else if (func_volatile(fnoid) != PROVOLATILE_IMMUTABLE)
+			{
+				/* No need to check for exec params inside a const */
+				doruntimeprune = true;
+				pinfo->do_initial_prune = true;
+			}
 			keyno++;
 		}
 	}
@@ -3346,6 +3408,12 @@ partkey_datum_from_expr(PartitionPruneContext *context,
 	}
 	else
 	{
+		/*
+		 * We should never see a non-Const in a step unless we're running in
+		 * the executor.
+		 */
+		Assert(context->planstate != NULL);
+
 		/*
 		 * When called from the executor we'll have a valid planstate so we
 		 * may be able to evaluate an expression which could not be folded to
@@ -3354,9 +3422,7 @@ partkey_datum_from_expr(PartitionPruneContext *context,
 		 * must be careful here to evaluate expressions containing PARAM_EXEC
 		 * Params only when told it's OK.
 		 */
-		if (context->planstate &&
-			(context->evalexecparams ||
-			 !context->exprhasexecparam[stateidx]))
+		if (context->evalexecparams || !context->exprhasexecparam[stateidx])
 		{
 			ExprState  *exprstate;
 			ExprContext *ectx;
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index bd64bed8fc..b30797434f 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -3036,6 +3036,24 @@ select * from listp where a = (select null::int);
 (7 rows)
 
 drop table listp;
+--
+-- check that stable query clauses are only used in run-time pruning
+--
+create table stable_qual_pruning (a timestamp) partition by range (a);
+create table stable_qual_pruning1 partition of stable_qual_pruning for values from ('2019-01-01') to ('2019-02-01');
+create table stable_qual_pruning2 partition of stable_qual_pruning for values from ('2019-02-01') to ('2019-03-01');
+-- timestamp < timestamptz comparison is only stable, not immutable
+explain (analyze, costs off, summary off, timing off)
+select * from stable_qual_pruning where a < '2019-02-01'::timestamptz;
+                                   QUERY PLAN                                   
+--------------------------------------------------------------------------------
+ Append (actual rows=0 loops=1)
+   Subplans Removed: 1
+   ->  Seq Scan on stable_qual_pruning1 (actual rows=0 loops=1)
+         Filter: (a < 'Fri Feb 01 00:00:00 2019 PST'::timestamp with time zone)
+(4 rows)
+
+drop table stable_qual_pruning;
 -- Ensure runtime pruning works with initplans params with boolean types
 create table boolvalues (value bool not null);
 insert into boolvalues values('t'),('f');
diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql
index 246c6274af..9e10819be0 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -754,6 +754,18 @@ select * from listp where a = (select null::int);
 
 drop table listp;
 
+--
+-- check that stable query clauses are only used in run-time pruning
+--
+create table stable_qual_pruning (a timestamp) partition by range (a);
+create table stable_qual_pruning1 partition of stable_qual_pruning for values from ('2019-01-01') to ('2019-02-01');
+create table stable_qual_pruning2 partition of stable_qual_pruning for values from ('2019-02-01') to ('2019-03-01');
+-- timestamp < timestamptz comparison is only stable, not immutable
+explain (analyze, costs off, summary off, timing off)
+select * from stable_qual_pruning where a < '2019-02-01'::timestamptz;
+
+drop table stable_qual_pruning;
+
 -- Ensure runtime pruning works with initplans params with boolean types
 create table boolvalues (value bool not null);
 insert into boolvalues values('t'),('f');
