approxidate: use deferred mday adjustments for "specials"

There are cases where the "wrap-to-yesterday" behavior of "tea" and "noon" should be reverted later on down the line, so that "today tea" and "tea today" won't yield different results. However, the logic of approxidate doesn't seem to lend itself particularly well to such cases. Start tackling the issue by reusing negative values of `tm->tm_mday` field for deferred date adjustments which can be easily reverted, so that the default logic of the special formats only applies if we don't get any explicit date (mday) specification. In particular, overwrite the field with -1 in "today" and "yesterday", so that those formats will be relative to the current date. That makes specifications like "tea yesterday" behave more sensibly: instead of going backwards to the last tea-time and then a day back, Git will now understand that as the tea-time of yesterday. Replace the call of `update_tm()` in `date_time()` with the assignment `tm->tm_mday = -2`. Add the corresponding code to handle that in `update_tm()`, wrapping to the previous day if the field still holds such assignment, meaning that we haven't seen any better specification for the day-of-month. On the other hand, `mday=-3` would mean going two days back and so on. Even though such functionality isn't actually needed by this patch, it won't add much complexity in the code and is rather natural way to handle such values. As `date_time()` won't no longer need the `now` struct, mark the associated function parameters as unused. The parameters themselves have to stay, however, as those functions are called through pointers in `approxidate_alpha`. Add relevant tests to cover the changes. Signed-off-by: Tuomas Ahola <taahol@utu.fi> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Tuomas Ahola committed May 21, 2026 at 13:54 UTC b809304101635d0fafc7644e82704ae653cacb07
2 files changed +25 -10
date.c
+21 -10
@@ -1071,13 +1071,22 @@ void datestamp(struct strbuf *out)
1071 /*
1072 * Relative time update (eg "2 days ago"). If we haven't set the time
1073 * yet, we need to set it from current time.
1074 + *
1075 + * The tm->tm_mday field has an additional logic of using negative values
1076 + * for date adjustments: -2 means yesterday and -3 the day before that,
1077 + * and so on. The idea is to deref such adjustments until we are sure
1078 + * there's no explicit mday specification in the approxidate string.
1079 */
1080 static time_t update_tm(struct tm *tm, struct tm *now, time_t sec)
1081 {
1082 time_t n;
1083
1079 - if (tm->tm_mday < 0)
1084 + if (tm->tm_mday < 0) {
1085 + int offset = tm->tm_mday + 1;
1086 + if (sec == 0 && offset < 0)
1087 + sec = -offset * 24*60*60;
1088 tm->tm_mday = now->tm_mday;
1089 + }
1090 if (tm->tm_mon < 0)
1091 tm->tm_mon = now->tm_mon;
1092 if (tm->tm_year < 0) {
@@ -1127,38 +1136,39 @@ static void date_now(struct tm *tm, struct tm *now, int *num)
1136 static void date_yesterday(struct tm *tm, struct tm *now, int *num)
1137 {
1138 *num = 0;
1139 + tm->tm_mday = -1;
1140 update_tm(tm, now, 24*60*60);
1141 }
1142
1133 -static void date_time(struct tm *tm, struct tm *now, int hour)
1143 +static void date_time(struct tm *tm, int hour)
1144 {
1145 /*
1146 * If we do not yet have a specified day, we'll use the most recent
1147 * version of "hour" relative to now. But that may be yesterday.
1148 */
1149 if (tm->tm_mday < 0 && tm->tm_hour < hour)
1140 - update_tm(tm, now, 24*60*60);
1150 + tm->tm_mday = -2; /* eventually handled by update_tm() */
1151 tm->tm_hour = hour;
1152 tm->tm_min = 0;
1153 tm->tm_sec = 0;
1154 }
1155
1146 -static void date_midnight(struct tm *tm, struct tm *now, int *num)
1156 +static void date_midnight(struct tm *tm, struct tm *now UNUSED, int *num)
1157 {
1158 pending_number(tm, num);
1149 - date_time(tm, now, 0);
1159 + date_time(tm, 0);
1160 }
1161
1152 -static void date_noon(struct tm *tm, struct tm *now, int *num)
1162 +static void date_noon(struct tm *tm, struct tm *now UNUSED, int *num)
1163 {
1164 pending_number(tm, num);
1155 - date_time(tm, now, 12);
1165 + date_time(tm, 12);
1166 }
1167
1158 -static void date_tea(struct tm *tm, struct tm *now, int *num)
1168 +static void date_tea(struct tm *tm, struct tm *now UNUSED, int *num)
1169 {
1170 pending_number(tm, num);
1161 - date_time(tm, now, 17);
1171 + date_time(tm, 17);
1172 }
1173
1174 static void date_pm(struct tm *tm, struct tm *now UNUSED, int *num)
@@ -1201,8 +1211,9 @@ static void date_today(struct tm *tm, struct tm *now, int *num)
1211 if (tm->tm_hour == now->tm_hour &&
1212 tm->tm_min == now->tm_min &&
1213 tm->tm_sec == now->tm_sec)
1204 - date_time(tm, now, 0);
1214 + date_time(tm, 0);
1215 *num = 0;
1216 + tm->tm_mday = -1;
1217 update_tm(tm, now, 0);
1218 }
1219
t/t0006-date.sh
+4
@@ -210,9 +210,13 @@ check_approxidate '3:00' '2009-08-30 03:00:00'
210 check_approxidate '15:00' '2009-08-30 15:00:00'
211 check_approxidate 'noon today' '2009-08-30 12:00:00'
212 check_approxidate 'today at noon' '2009-08-30 12:00:00' '-12 hours'
213 +check_approxidate 'noon today' '2009-09-01 12:00:00' '+36 hours'
214 check_approxidate 'noon yesterday' '2009-08-29 12:00:00'
215 +check_approxidate 'noon yesterday' '2009-08-29 12:00:00' '-12 hours'
216 check_approxidate 'last Friday at noon' '2009-08-28 12:00:00'
217 check_approxidate 'last Friday at noon' '2009-08-28 12:00:00' '-12 hours'
218 +check_approxidate 'tea last saturday' '2009-08-29 17:00:00'
219 +check_approxidate 'tea last saturday' '2009-08-29 17:00:00' '-12 hours'
220 check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00'
221 check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00' '-12 hours'
222 check_approxidate 'January 5th today pm' '2009-01-30 12:00:00'