From 7d4db18619b1c85ee17f9c1a2f088587195ed8bf Mon Sep 17 00:00:00 2001
From: Moaaz Assali <ma5679@nyu.edu>
Date: Tue, 27 Feb 2024 07:30:04 +0000
Subject: [PATCH v1] Fix for edge case in date_bin() function.

Consider date_bin('30 minutes'::interval, '2024-01-01 15:00:00'::timestamp, '2024-01-01 17:00:00'::timestamp);
This will return '2024-01-01 14:30:00' instead of '2024-01-01 15:00:00' despite '2024-01-01 15:00:00' being the valid 
binned date for the time '15:00:00', and this commit fixes that.

The reason this happens is that the code in timestamp_bin() that allows for correct date binning when source 
timestamp < origin timestamp subtracts one stride in all cases.
Although that is correct in the majority of cases, it fails the edge case when the source timestamp is 
exactly equivalent to a valid binned date as in the example mentioned above.

To account for this edge, we simply add another condition in the if statement to not perform 
the subtraction by one stride interval if the time difference is divisible by the stride.
---
 src/backend/utils/adt/timestamp.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index ed03c50a6d..1e6b6e626f 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -4573,9 +4573,12 @@ timestamp_bin(PG_FUNCTION_ARGS)
 
 	/*
 	 * Make sure the returned timestamp is at the start of the bin, even if
-	 * the origin is in the future.
+	 * the origin is in the future. The condition (tm_diff % stride_usecs !=
+	 * 0) is needed to avoid unnecessary decrement when target timestamp is
+	 * already a valid binned date. If stride=30mins, timestamp=15:00:00,
+	 * origin=17:00:00, we should return 15:00, not 14:30.
 	 */
-	if (origin > timestamp && stride_usecs > 1)
+	if (origin > timestamp && stride_usecs > 1 && tm_diff % stride_usecs != 0)
 		tm_delta -= stride_usecs;
 
 	result = origin + tm_delta;
-- 
2.34.1

