diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTableHeader.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTableHeader.tsx
index 150d3393da45..d328b2b08a9e 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportTableHeader.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportTableHeader.tsx
@@ -16,6 +16,7 @@ type SearchTableHeaderProps = {
sortOrder?: SortOrder;
onSortPress: (column: SearchSortBy, order: SortOrder) => void;
dateColumnSize: TableColumnSize;
+ postedColumnSize: TableColumnSize;
amountColumnSize: TableColumnSize;
taxAmountColumnSize: TableColumnSize;
shouldShowSorting: boolean;
@@ -27,6 +28,7 @@ function MoneyRequestReportTableHeader({
sortOrder,
onSortPress,
dateColumnSize,
+ postedColumnSize,
shouldShowSorting,
columns,
amountColumnSize,
@@ -86,6 +88,7 @@ function MoneyRequestReportTableHeader({
columns={orderedColumnConfig}
shouldShowColumn={shouldShowColumn}
dateColumnSize={dateColumnSize}
+ postedColumnSize={postedColumnSize}
amountColumnSize={amountColumnSize}
taxAmountColumnSize={taxAmountColumnSize}
shouldShowSorting={shouldShowSorting}
diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx
index 293b00073ffd..9b1830cad118 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx
@@ -68,6 +68,9 @@ type MoneyRequestReportTransactionItemProps = {
/** The size of the date column */
dateColumnSize: TableColumnSize;
+ /** The size of the posted column */
+ postedColumnSize: TableColumnSize;
+
/** The size of the amount column */
amountColumnSize: TableColumnSize;
@@ -122,6 +125,7 @@ function MoneyRequestReportTransactionItemBody({
handleLongPress,
columns,
dateColumnSize,
+ postedColumnSize,
amountColumnSize,
taxAmountColumnSize,
scrollToNewTransaction,
@@ -236,6 +240,7 @@ function MoneyRequestReportTransactionItemBody({
transactionThreadReportID={transactionThreadReportID}
isSelected={isSelected}
dateColumnSize={dateColumnSize}
+ postedColumnSize={postedColumnSize}
amountColumnSize={amountColumnSize}
taxAmountColumnSize={taxAmountColumnSize}
shouldShowTooltip
diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
index db7c07293d98..1409953908fc 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
@@ -58,6 +58,7 @@ import {compareValues, getColumnsToShow, getTableMinWidth, hasFlexColumn, isTran
import {getPendingSubmitFollowUpAction} from '@libs/telemetry/submitFollowUpAction';
import {transactionHasRBR} from '@libs/TransactionPreviewUtils';
import {getTransactionPendingAction, getVisibleTransactionViolations, isTransactionPendingDelete, shouldShowExpenseBreakdown} from '@libs/TransactionUtils';
+import shouldShowTransactionPostedYear from '@libs/TransactionUtils/shouldShowTransactionPostedYear';
import shouldShowTransactionYear from '@libs/TransactionUtils/shouldShowTransactionYear';
import isReportOpenInSuperWideRHP from '@navigation/helpers/isReportOpenInSuperWideRHP';
@@ -571,14 +572,16 @@ function MoneyRequestReportTransactionList({
[navigateToTransactionThread, reportActions, sortedTransactions, report, visualOrderTransactionIDs],
);
- const {amountColumnSize, dateColumnSize, taxAmountColumnSize} = useMemo(() => {
+ const {amountColumnSize, dateColumnSize, postedColumnSize, taxAmountColumnSize} = useMemo(() => {
const isAmountColumnWide = transactions.some((transaction) => isTransactionAmountTooLong(transaction));
const isTaxAmountColumnWide = transactions.some((transaction) => isTransactionTaxAmountTooLong(transaction));
const shouldShowYearForSomeTransaction = transactions.some((transaction) => shouldShowTransactionYear(transaction));
+ const shouldShowPostedYearForSomeTransaction = transactions.some((transaction) => shouldShowTransactionPostedYear(transaction));
return {
amountColumnSize: isAmountColumnWide ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL,
taxAmountColumnSize: isTaxAmountColumnWide ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL,
dateColumnSize: shouldShowYearForSomeTransaction ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL,
+ postedColumnSize: shouldShowPostedYearForSomeTransaction ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL,
};
}, [transactions]);
@@ -722,6 +725,7 @@ function MoneyRequestReportTransactionList({
handleOnPress={handleOnPress}
handleLongPress={handleLongPress}
dateColumnSize={dateColumnSize}
+ postedColumnSize={postedColumnSize}
amountColumnSize={amountColumnSize}
taxAmountColumnSize={taxAmountColumnSize}
scrollToNewTransaction={transaction.transactionID === newTransactions?.at(0)?.transactionID ? scrollToNewTransaction : undefined}
@@ -831,6 +835,7 @@ function MoneyRequestReportTransactionList({
shouldRemoveTotalColumnFlex={hasFlexColumn(columnsToShow)}
columns={columnsToShow}
dateColumnSize={dateColumnSize}
+ postedColumnSize={postedColumnSize}
amountColumnSize={amountColumnSize}
taxAmountColumnSize={taxAmountColumnSize}
onSortPress={(selectedSortBy, selectedSortOrder) => {
diff --git a/src/components/TransactionItemRow/TransactionItemRowWide.tsx b/src/components/TransactionItemRow/TransactionItemRowWide.tsx
index c4b8ab123a44..0773606c85d3 100644
--- a/src/components/TransactionItemRow/TransactionItemRowWide.tsx
+++ b/src/components/TransactionItemRow/TransactionItemRowWide.tsx
@@ -40,6 +40,7 @@ import {
isScanning,
isTimeRequest,
} from '@libs/TransactionUtils';
+import getFormattedPostedDate from '@libs/TransactionUtils/getFormattedPostedDate';
import variables from '@styles/variables';
@@ -248,7 +249,7 @@ function TransactionItemRowWide({
style={[StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.POSTED, {isPostedColumnWide})]}
>
diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts
index 4662b5aae58f..67c2077dfef0 100644
--- a/src/libs/SearchUIUtils.ts
+++ b/src/libs/SearchUIUtils.ts
@@ -218,6 +218,8 @@ import {
shouldShowAttendees as shouldShowAttendeesUtils,
shouldShowViolation,
} from './TransactionUtils';
+import getFormattedPostedDate from './TransactionUtils/getFormattedPostedDate';
+import shouldShowTransactionPostedYear from './TransactionUtils/shouldShowTransactionPostedYear';
import {isInvalidMerchantValue} from './ValidationUtils';
type ColumnSortMapping = Partial>;
@@ -1216,8 +1218,7 @@ function getTransactionItemCommonFormattedProperties(
const submitted = report?.submitted;
const approved = report?.approved;
- // Posted date is in the YYYYMMDD format, so we format it to YYYY-MM-DD here since JS's Date constructor interprets it as an invalid date.
- const posted = !transactionItem?.posted ? '' : `${transactionItem?.posted.slice(0, 4)}-${transactionItem?.posted.slice(4, 6)}-${transactionItem?.posted.slice(6, 8)}`;
+ const posted = getFormattedPostedDate(transactionItem?.posted);
return {
formattedFrom,
@@ -1526,11 +1527,9 @@ function shouldShowYear(
result.shouldShowYearApproved = true;
}
- // Posted date is in the YYYYMMDD format, so we extract the year manually here since JS's Date constructor interprets it as an invalid date.
// Latch once true so a later current-year date can't reset it (the row renderer latches the same way).
- if (!result.shouldShowYearPosted && item?.posted) {
- const postedYear = parseInt(item.posted.slice(0, 4), 10);
- result.shouldShowYearPosted = postedYear !== currentYear;
+ if (!result.shouldShowYearPosted && shouldShowTransactionPostedYear(item)) {
+ result.shouldShowYearPosted = true;
}
}
@@ -1559,11 +1558,9 @@ function shouldShowYear(
result.shouldShowYearApproved = true;
}
- // Posted date is in the YYYYMMDD format, so we extract the year manually here since JS's Date constructor interprets it as an invalid date.
// Latch once true so a later current-year date can't reset it (the row renderer latches the same way).
- if (!result.shouldShowYearPosted && item?.posted) {
- const postedYear = parseInt(item.posted.slice(0, 4), 10);
- result.shouldShowYearPosted = postedYear !== currentYear;
+ if (!result.shouldShowYearPosted && shouldShowTransactionPostedYear(item)) {
+ result.shouldShowYearPosted = true;
}
const exportedAction = item.reportID ? lastExportedActionByReportID.get(item.reportID) : undefined;
@@ -1760,7 +1757,6 @@ type PreprocessingContext = {
shouldShowAmountInWideColumn: boolean;
shouldShowTaxAmountInWideColumn: boolean;
hasDeletedTransaction: boolean;
- currentYear: number;
};
function createPreprocessingContext(): PreprocessingContext {
@@ -1787,7 +1783,6 @@ function createPreprocessingContext(): PreprocessingContext {
shouldShowAmountInWideColumn: false,
shouldShowTaxAmountInWideColumn: false,
hasDeletedTransaction: false,
- currentYear: new Date().getFullYear(),
};
}
@@ -1905,11 +1900,8 @@ function processTransactionEntry(ctx: PreprocessingContext, transaction: OnyxTyp
ctx.shouldShowYearApproved = true;
}
- if (!ctx.shouldShowYearPosted && transaction?.posted) {
- const postedYear = parseInt(transaction.posted.slice(0, 4), 10);
- if (postedYear !== ctx.currentYear) {
- ctx.shouldShowYearPosted = true;
- }
+ if (!ctx.shouldShowYearPosted && shouldShowTransactionPostedYear(transaction)) {
+ ctx.shouldShowYearPosted = true;
}
// Optimistic: works when report-action keys precede transaction keys (common case).
@@ -1980,7 +1972,7 @@ function resolveExportedYearFlags(ctx: PreprocessingContext, data: OnyxTypes.Sea
* (key classification, violations, export/action maps, year flags, column indicators)
* needed by getTransactionsSections and getReportSections.
*/
-function classifyAndPreprocess(data: OnyxTypes.SearchResults['data']): Omit {
+function classifyAndPreprocess(data: OnyxTypes.SearchResults['data']): Omit {
const ctx = createPreprocessingContext();
for (const key of Object.keys(data)) {
diff --git a/src/libs/TransactionUtils/getFormattedPostedDate.ts b/src/libs/TransactionUtils/getFormattedPostedDate.ts
new file mode 100644
index 000000000000..39622d896d75
--- /dev/null
+++ b/src/libs/TransactionUtils/getFormattedPostedDate.ts
@@ -0,0 +1,11 @@
+function getFormattedPostedDate(posted?: string): string {
+ if (!posted) {
+ return '';
+ }
+ if (!/^\d{8}$/.test(posted)) {
+ return posted;
+ }
+ return `${posted.slice(0, 4)}-${posted.slice(4, 6)}-${posted.slice(6, 8)}`;
+}
+
+export default getFormattedPostedDate;
diff --git a/src/libs/TransactionUtils/shouldShowTransactionPostedYear.ts b/src/libs/TransactionUtils/shouldShowTransactionPostedYear.ts
new file mode 100644
index 000000000000..a9b2ac7f15b8
--- /dev/null
+++ b/src/libs/TransactionUtils/shouldShowTransactionPostedYear.ts
@@ -0,0 +1,15 @@
+import DateUtils from '@libs/DateUtils';
+
+import type {OnyxInputOrEntry, Transaction} from '@src/types/onyx';
+
+import getFormattedPostedDate from './getFormattedPostedDate';
+
+function shouldShowTransactionPostedYear(transaction: OnyxInputOrEntry) {
+ const posted = transaction?.posted;
+ if (!posted) {
+ return false;
+ }
+ return DateUtils.doesDateBelongToAPastYear(getFormattedPostedDate(posted));
+}
+
+export default shouldShowTransactionPostedYear;
diff --git a/tests/unit/getFormattedPostedDateTest.ts b/tests/unit/getFormattedPostedDateTest.ts
new file mode 100644
index 000000000000..1323a35bef21
--- /dev/null
+++ b/tests/unit/getFormattedPostedDateTest.ts
@@ -0,0 +1,22 @@
+import getFormattedPostedDate from '@libs/TransactionUtils/getFormattedPostedDate';
+
+describe('getFormattedPostedDate', () => {
+ it('converts a raw YYYYMMDD card posted date to YYYY-MM-DD', () => {
+ expect(getFormattedPostedDate('20260710')).toBe('2026-07-10');
+ expect(getFormattedPostedDate('20251231')).toBe('2025-12-31');
+ });
+
+ it('passes an already-ISO date through unchanged (idempotent)', () => {
+ expect(getFormattedPostedDate('2026-07-10')).toBe('2026-07-10');
+ });
+
+ it('returns an empty string for empty/undefined input', () => {
+ expect(getFormattedPostedDate(undefined)).toBe('');
+ expect(getFormattedPostedDate('')).toBe('');
+ });
+
+ it('passes any non-YYYYMMDD string through unchanged', () => {
+ expect(getFormattedPostedDate('2026-7-1')).toBe('2026-7-1');
+ expect(getFormattedPostedDate('not-a-date')).toBe('not-a-date');
+ });
+});
diff --git a/tests/unit/shouldShowTransactionPostedYearTest.ts b/tests/unit/shouldShowTransactionPostedYearTest.ts
new file mode 100644
index 000000000000..67c4349717d4
--- /dev/null
+++ b/tests/unit/shouldShowTransactionPostedYearTest.ts
@@ -0,0 +1,26 @@
+import shouldShowTransactionPostedYear from '@libs/TransactionUtils/shouldShowTransactionPostedYear';
+
+import createRandomTransaction from '../utils/collections/transaction';
+
+const currentYear = new Date().getFullYear();
+const transactionWithPosted = (posted?: string) => ({...createRandomTransaction(0), posted});
+
+describe('shouldShowTransactionPostedYear', () => {
+ it('returns true when the raw YYYYMMDD posted date is in a past year', () => {
+ expect(shouldShowTransactionPostedYear(transactionWithPosted(`${currentYear - 1}0701`))).toBe(true);
+ });
+
+ it('returns false when the raw YYYYMMDD posted date is in the current year', () => {
+ expect(shouldShowTransactionPostedYear(transactionWithPosted(`${currentYear}0701`))).toBe(false);
+ });
+
+ it('returns false when there is no posted date', () => {
+ expect(shouldShowTransactionPostedYear(transactionWithPosted(undefined))).toBe(false);
+ expect(shouldShowTransactionPostedYear(transactionWithPosted(''))).toBe(false);
+ });
+
+ it('returns false for a nullish transaction', () => {
+ expect(shouldShowTransactionPostedYear(undefined)).toBe(false);
+ expect(shouldShowTransactionPostedYear(null)).toBe(false);
+ });
+});