Skip to content

Commit 180b631

Browse files
committed
Issue 54160: Non US date parsing in the app
1 parent 80340af commit 180b631

5 files changed

Lines changed: 417 additions & 2 deletions

File tree

packages/components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@labkey/components",
3-
"version": "6.66.0",
3+
"version": "6.66.1-fb-nonUSParsing.1",
44
"description": "Components, models, actions, and utility functions for LabKey applications and pages",
55
"sideEffects": false,
66
"files": [

packages/components/releaseNotes/components.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
# @labkey/components
22
Components, models, actions, and utility functions for LabKey applications and pages
33

4+
### version 6.65.X
5+
*Released*: X October 2025
6+
- Issue 54160: Non US date parsing in the app
7+
- Added `getAltNonUSParseFormats` date utility function to provide alternative parse formats for common non-US date/datetime formats
8+
- Update DatePickerInput to use alternative parse formats when server date format is non-US
9+
- Update parseDate utility function to use alternative non-US parse formats
10+
411
### version 6.66.0
512
*Released*: 23 October 2025
613
- ChartBuilderModal support for bar/line chart aggregate method and error bar options

packages/components/src/internal/components/forms/input/DatePickerInput.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import DatePicker from 'react-datepicker';
1919
import { FormsyInjectedProps, withFormsy } from '../formsy';
2020
import { FieldLabel } from '../FieldLabel';
2121
import {
22+
getAltNonUSParseFormats,
2223
getDateFromISO,
2324
getDateTimeDisplayValue,
2425
getJsonDateFormatString,
@@ -257,7 +258,7 @@ export class DatePickerInputImpl extends DisableableInput<DatePickerInputImplPro
257258
autoComplete="off"
258259
autoFocus={autoFocus}
259260
className={inputClassName}
260-
dateFormat={dateFormat}
261+
dateFormat={getAltNonUSParseFormats(dateFormat)}
261262
disabled={isDisabled}
262263
id={queryColumn.fieldKey}
263264
isClearable={isClearable}

packages/components/src/internal/util/Date.test.ts

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
formatDateTime,
2727
formatTime,
2828
generateNameWithTimestamp,
29+
getAltNonUSParseFormats,
2930
getColDateFormat,
3031
getColFormattedDateFilterValue,
3132
getColFormattedTimeFilterValue,
@@ -751,6 +752,7 @@ describe('Date Utilities', () => {
751752
});
752753

753754
test('valid date with dateFormat', () => {
755+
LABKEY.useMDYDateParsing = true;
754756
expect(parseDate('01:02 2022-04-19', 'HH:mm yyyy-MM-dd').toString()).toContain('Apr 19 2022');
755757
expect(parseDate('19/04/2022', 'dd/MM/yyyy').toString()).toContain('Apr 19 2022');
756758
expect(parseDate('4/11/2022', 'dd/MM/yyyy').toString()).toContain('Nov 04 2022');
@@ -763,6 +765,8 @@ describe('Date Utilities', () => {
763765
expect(parseDate('22-04-11', 'YY-MM-DD').toString()).toContain('Apr 11 2022');
764766
expect(parseDate('22/04/11', 'yy/MM/dd').toString()).toContain('Apr 11 2022');
765767
expect(parseDate('22/04/11', 'YY/MM/DD').toString()).toContain('Apr 11 2022');
768+
// because useMDYDateParsing = true and the format doesn't match the provided string, this won't be parsed as Nov 4
769+
expect(parseDate('4/11/2022', 'dd-MM-yyyy').toString()).toContain('Apr 11 2022');
766770
});
767771

768772
test('minDate', () => {
@@ -788,6 +792,26 @@ describe('Date Utilities', () => {
788792
'Sep 11 1985 12:50:22'
789793
);
790794
});
795+
796+
test('useMDYDateParsing = false', () => {
797+
LABKEY.useMDYDateParsing = false;
798+
expect(parseDate('4/11/2022', 'dd/MM/yyyy').toString()).toContain('Nov 04 2022');
799+
expect(parseDate('4/11/2022', 'ddMMMMyy').toString()).toContain('Nov 04 2022');
800+
expect(parseDate('4/11/2022', 'dd-MM-yyyy').toString()).toContain('Nov 04 2022');
801+
expect(parseDate('4/11/2022', 'dd/MM/yy').toString()).toContain('Nov 04 2022');
802+
expect(parseDate('4/11/2022 11:30', 'dd/MM/yy').toString()).toContain('Nov 04 2022 11:30');
803+
expect(parseDate('4/11/2022 11:30 PM', 'dd/MM/yy').toString()).toContain('Nov 04 2022 23:30');
804+
expect(parseDate('4/11/2022 11:30', 'dd-MM-yy').toString()).toContain('Nov 04 2022 11:30');
805+
expect(parseDate('4/11/2022 11:30 PM', 'dd-MM-yy').toString()).toContain('Nov 04 2022 23:30');
806+
expect(parseDate('4/11/2022 11:30:30 PM', 'dd/MM/yy HH:mm').toString()).toContain('Nov 04 2022 23:30:30');
807+
expect(parseDate('4/11/2022 11:30', 'dd-MM-yy hh:mm:ss').toString()).toContain('Nov 04 2022 11:30');
808+
expect(parseDate('4/11/2022 11:30:30.123 PM', 'dd-MM-yy hh:mm').toString()).toContain('Nov 04 2022 23:30:30');
809+
// without a dateFormat, use DMY parsing even if useMDYDateParsing = false
810+
expect(parseDate('4/11/2022').toString()).toContain('Apr 11 2022');
811+
expect(parseDate('4/11/2022 11:30').toString()).toContain('Apr 11 2022');
812+
expect(parseDate('4/11/2022 11:30 PM').toString()).toContain('Apr 11 2022');
813+
LABKEY.useMDYDateParsing = true;
814+
});
791815
});
792816

793817
describe('getPickerTimeFormatWithPrecision', () => {
@@ -1172,4 +1196,307 @@ describe('Date Utilities', () => {
11721196
expect(checkFormat('XXX', tz)).toBe('-05:00');
11731197
});
11741198
});
1199+
1200+
describe('getAltDateParseFormats', () => {
1201+
1202+
it('when useMDYDateParsing is true', () => {
1203+
LABKEY.useMDYDateParsing = true;
1204+
expect(getAltNonUSParseFormats('MM-DD-yy')).toEqual('MM-DD-yy');
1205+
expect(getAltNonUSParseFormats('dd-MM-yy')).toEqual('dd-MM-yy');
1206+
});
1207+
1208+
it('when current format does not start with d', () => {
1209+
LABKEY.useMDYDateParsing = false;
1210+
expect(getAltNonUSParseFormats('MM-DD-yy')).toEqual('MM-DD-yy');
1211+
expect(getAltNonUSParseFormats('yy-MM-DD')).toEqual('yy-MM-DD');
1212+
LABKEY.useMDYDateParsing = true;
1213+
});
1214+
1215+
it('when useMDYDateParsing is false and the current format starts with d, without inputDateStr', () => {
1216+
LABKEY.useMDYDateParsing = false;
1217+
expect(getAltNonUSParseFormats('DD-MM-yy')).toEqual(
1218+
["DD-MM-yy",
1219+
"dd-MM-yy",
1220+
"dd/MM/yy",
1221+
"ddMMyy",
1222+
"dd-MM-yyyy",
1223+
"dd/MM/yyyy",
1224+
"ddMMyyyy",
1225+
"dd-MMM-yy",
1226+
"dd/MMM/yy",
1227+
"ddMMMyy",
1228+
"dd-MMM-yyyy",
1229+
"dd/MMM/yyyy",
1230+
"ddMMMyyyy",
1231+
"dd-MMMM-yy",
1232+
"dd/MMMM/yy",
1233+
"ddMMMMyy",
1234+
"dd-MMMM-yyyy",
1235+
"dd/MMMM/yyyy",
1236+
"ddMMMMyyyy",
1237+
"DD-MM-yy hh:mm:ss.SSS a",
1238+
"DD-MM-yy hh:mm:ss a",
1239+
"DD-MM-yy hh:mm a",
1240+
"DD-MM-yy hh a",
1241+
"DD-MM-yy HH:mm:ss.SSS",
1242+
"DD-MM-yy HH:mm:ss",
1243+
"DD-MM-yy HH:mm",
1244+
"DD-MM-yy HH",
1245+
"dd-MM-yy hh:mm:ss.SSS a",
1246+
"dd-MM-yy hh:mm:ss a",
1247+
"dd-MM-yy hh:mm a",
1248+
"dd-MM-yy hh a",
1249+
"dd-MM-yy HH:mm:ss.SSS",
1250+
"dd-MM-yy HH:mm:ss",
1251+
"dd-MM-yy HH:mm",
1252+
"dd-MM-yy HH",
1253+
"dd/MM/yy hh:mm:ss.SSS a",
1254+
"dd/MM/yy hh:mm:ss a",
1255+
"dd/MM/yy hh:mm a",
1256+
"dd/MM/yy hh a",
1257+
"dd/MM/yy HH:mm:ss.SSS",
1258+
"dd/MM/yy HH:mm:ss",
1259+
"dd/MM/yy HH:mm",
1260+
"dd/MM/yy HH",
1261+
"ddMMyy hh:mm:ss.SSS a",
1262+
"ddMMyy hh:mm:ss a",
1263+
"ddMMyy hh:mm a",
1264+
"ddMMyy hh a",
1265+
"ddMMyy HH:mm:ss.SSS",
1266+
"ddMMyy HH:mm:ss",
1267+
"ddMMyy HH:mm",
1268+
"ddMMyy HH",
1269+
"dd-MM-yyyy hh:mm:ss.SSS a",
1270+
"dd-MM-yyyy hh:mm:ss a",
1271+
"dd-MM-yyyy hh:mm a",
1272+
"dd-MM-yyyy hh a",
1273+
"dd-MM-yyyy HH:mm:ss.SSS",
1274+
"dd-MM-yyyy HH:mm:ss",
1275+
"dd-MM-yyyy HH:mm",
1276+
"dd-MM-yyyy HH",
1277+
"dd/MM/yyyy hh:mm:ss.SSS a",
1278+
"dd/MM/yyyy hh:mm:ss a",
1279+
"dd/MM/yyyy hh:mm a",
1280+
"dd/MM/yyyy hh a",
1281+
"dd/MM/yyyy HH:mm:ss.SSS",
1282+
"dd/MM/yyyy HH:mm:ss",
1283+
"dd/MM/yyyy HH:mm",
1284+
"dd/MM/yyyy HH",
1285+
"ddMMyyyy hh:mm:ss.SSS a",
1286+
"ddMMyyyy hh:mm:ss a",
1287+
"ddMMyyyy hh:mm a",
1288+
"ddMMyyyy hh a",
1289+
"ddMMyyyy HH:mm:ss.SSS",
1290+
"ddMMyyyy HH:mm:ss",
1291+
"ddMMyyyy HH:mm",
1292+
"ddMMyyyy HH",
1293+
"dd-MMM-yy hh:mm:ss.SSS a",
1294+
"dd-MMM-yy hh:mm:ss a",
1295+
"dd-MMM-yy hh:mm a",
1296+
"dd-MMM-yy hh a",
1297+
"dd-MMM-yy HH:mm:ss.SSS",
1298+
"dd-MMM-yy HH:mm:ss",
1299+
"dd-MMM-yy HH:mm",
1300+
"dd-MMM-yy HH",
1301+
"dd/MMM/yy hh:mm:ss.SSS a",
1302+
"dd/MMM/yy hh:mm:ss a",
1303+
"dd/MMM/yy hh:mm a",
1304+
"dd/MMM/yy hh a",
1305+
"dd/MMM/yy HH:mm:ss.SSS",
1306+
"dd/MMM/yy HH:mm:ss",
1307+
"dd/MMM/yy HH:mm",
1308+
"dd/MMM/yy HH",
1309+
"ddMMMyy hh:mm:ss.SSS a",
1310+
"ddMMMyy hh:mm:ss a",
1311+
"ddMMMyy hh:mm a",
1312+
"ddMMMyy hh a",
1313+
"ddMMMyy HH:mm:ss.SSS",
1314+
"ddMMMyy HH:mm:ss",
1315+
"ddMMMyy HH:mm",
1316+
"ddMMMyy HH",
1317+
"dd-MMM-yyyy hh:mm:ss.SSS a",
1318+
"dd-MMM-yyyy hh:mm:ss a",
1319+
"dd-MMM-yyyy hh:mm a",
1320+
"dd-MMM-yyyy hh a",
1321+
"dd-MMM-yyyy HH:mm:ss.SSS",
1322+
"dd-MMM-yyyy HH:mm:ss",
1323+
"dd-MMM-yyyy HH:mm",
1324+
"dd-MMM-yyyy HH",
1325+
"dd/MMM/yyyy hh:mm:ss.SSS a",
1326+
"dd/MMM/yyyy hh:mm:ss a",
1327+
"dd/MMM/yyyy hh:mm a",
1328+
"dd/MMM/yyyy hh a",
1329+
"dd/MMM/yyyy HH:mm:ss.SSS",
1330+
"dd/MMM/yyyy HH:mm:ss",
1331+
"dd/MMM/yyyy HH:mm",
1332+
"dd/MMM/yyyy HH",
1333+
"ddMMMyyyy hh:mm:ss.SSS a",
1334+
"ddMMMyyyy hh:mm:ss a",
1335+
"ddMMMyyyy hh:mm a",
1336+
"ddMMMyyyy hh a",
1337+
"ddMMMyyyy HH:mm:ss.SSS",
1338+
"ddMMMyyyy HH:mm:ss",
1339+
"ddMMMyyyy HH:mm",
1340+
"ddMMMyyyy HH",
1341+
"dd-MMMM-yy hh:mm:ss.SSS a",
1342+
"dd-MMMM-yy hh:mm:ss a",
1343+
"dd-MMMM-yy hh:mm a",
1344+
"dd-MMMM-yy hh a",
1345+
"dd-MMMM-yy HH:mm:ss.SSS",
1346+
"dd-MMMM-yy HH:mm:ss",
1347+
"dd-MMMM-yy HH:mm",
1348+
"dd-MMMM-yy HH",
1349+
"dd/MMMM/yy hh:mm:ss.SSS a",
1350+
"dd/MMMM/yy hh:mm:ss a",
1351+
"dd/MMMM/yy hh:mm a",
1352+
"dd/MMMM/yy hh a",
1353+
"dd/MMMM/yy HH:mm:ss.SSS",
1354+
"dd/MMMM/yy HH:mm:ss",
1355+
"dd/MMMM/yy HH:mm",
1356+
"dd/MMMM/yy HH",
1357+
"ddMMMMyy hh:mm:ss.SSS a",
1358+
"ddMMMMyy hh:mm:ss a",
1359+
"ddMMMMyy hh:mm a",
1360+
"ddMMMMyy hh a",
1361+
"ddMMMMyy HH:mm:ss.SSS",
1362+
"ddMMMMyy HH:mm:ss",
1363+
"ddMMMMyy HH:mm",
1364+
"ddMMMMyy HH",
1365+
"dd-MMMM-yyyy hh:mm:ss.SSS a",
1366+
"dd-MMMM-yyyy hh:mm:ss a",
1367+
"dd-MMMM-yyyy hh:mm a",
1368+
"dd-MMMM-yyyy hh a",
1369+
"dd-MMMM-yyyy HH:mm:ss.SSS",
1370+
"dd-MMMM-yyyy HH:mm:ss",
1371+
"dd-MMMM-yyyy HH:mm",
1372+
"dd-MMMM-yyyy HH",
1373+
"dd/MMMM/yyyy hh:mm:ss.SSS a",
1374+
"dd/MMMM/yyyy hh:mm:ss a",
1375+
"dd/MMMM/yyyy hh:mm a",
1376+
"dd/MMMM/yyyy hh a",
1377+
"dd/MMMM/yyyy HH:mm:ss.SSS",
1378+
"dd/MMMM/yyyy HH:mm:ss",
1379+
"dd/MMMM/yyyy HH:mm",
1380+
"dd/MMMM/yyyy HH",
1381+
"ddMMMMyyyy hh:mm:ss.SSS a",
1382+
"ddMMMMyyyy hh:mm:ss a",
1383+
"ddMMMMyyyy hh:mm a",
1384+
"ddMMMMyyyy hh a",
1385+
"ddMMMMyyyy HH:mm:ss.SSS",
1386+
"ddMMMMyyyy HH:mm:ss",
1387+
"ddMMMMyyyy HH:mm",
1388+
"ddMMMMyyyy HH",]);
1389+
1390+
LABKEY.useMDYDateParsing = true;
1391+
});
1392+
1393+
it('useMDYDateParsing is false, with inputDateStr that is a date', () => {
1394+
LABKEY.useMDYDateParsing = false;
1395+
expect(getAltNonUSParseFormats('dd-MM-yy', '02-04-2025')).toEqual([
1396+
"dd-MM-yy",
1397+
"dd-MM-yyyy",
1398+
"dd-MMM-yy",
1399+
"dd-MMM-yyyy",
1400+
"dd-MMMM-yy",
1401+
"dd-MMMM-yyyy",
1402+
]);
1403+
expect(getAltNonUSParseFormats('dd-MM-yy', '02/04/2025')).toEqual([
1404+
"dd-MM-yy",
1405+
"dd/MM/yy",
1406+
"dd/MM/yyyy",
1407+
"dd/MMM/yy",
1408+
"dd/MMM/yyyy",
1409+
"dd/MMMM/yy",
1410+
"dd/MMMM/yyyy",
1411+
]);
1412+
expect(getAltNonUSParseFormats('dd/MM/yy', '02042025')).toEqual([
1413+
"dd/MM/yy",
1414+
"ddMMyy",
1415+
"ddMMyyyy",
1416+
"ddMMMyy",
1417+
"ddMMMyyyy",
1418+
"ddMMMMyy",
1419+
"ddMMMMyyyy",
1420+
]);
1421+
1422+
LABKEY.useMDYDateParsing = true;
1423+
});
1424+
1425+
it('useMDYDateParsing is false, with inputDateStr that is a datetime', () => {
1426+
LABKEY.useMDYDateParsing = false;
1427+
expect(getAltNonUSParseFormats('dd-MM-yy', '02-04-2025 11:30')).toEqual([
1428+
"dd-MM-yy",
1429+
"dd-MM-yyyy",
1430+
"dd-MMM-yy",
1431+
"dd-MMM-yyyy",
1432+
"dd-MMMM-yy",
1433+
"dd-MMMM-yyyy",
1434+
"dd-MM-yy HH:mm:ss.SSS",
1435+
"dd-MM-yy HH:mm:ss",
1436+
"dd-MM-yy HH:mm",
1437+
"dd-MM-yy HH",
1438+
"dd-MM-yyyy HH:mm:ss.SSS",
1439+
"dd-MM-yyyy HH:mm:ss",
1440+
"dd-MM-yyyy HH:mm",
1441+
"dd-MM-yyyy HH",
1442+
"dd-MMM-yy HH:mm:ss.SSS",
1443+
"dd-MMM-yy HH:mm:ss",
1444+
"dd-MMM-yy HH:mm",
1445+
"dd-MMM-yy HH",
1446+
"dd-MMM-yyyy HH:mm:ss.SSS",
1447+
"dd-MMM-yyyy HH:mm:ss",
1448+
"dd-MMM-yyyy HH:mm",
1449+
"dd-MMM-yyyy HH",
1450+
"dd-MMMM-yy HH:mm:ss.SSS",
1451+
"dd-MMMM-yy HH:mm:ss",
1452+
"dd-MMMM-yy HH:mm",
1453+
"dd-MMMM-yy HH",
1454+
"dd-MMMM-yyyy HH:mm:ss.SSS",
1455+
"dd-MMMM-yyyy HH:mm:ss",
1456+
"dd-MMMM-yyyy HH:mm",
1457+
"dd-MMMM-yyyy HH",
1458+
]);
1459+
expect(getAltNonUSParseFormats('dd-MM-yy', '02/04/2025 11:30 PM')).toEqual([
1460+
"dd-MM-yy",
1461+
"dd/MM/yy",
1462+
"dd/MM/yyyy",
1463+
"dd/MMM/yy",
1464+
"dd/MMM/yyyy",
1465+
"dd/MMMM/yy",
1466+
"dd/MMMM/yyyy",
1467+
"dd-MM-yy hh:mm:ss.SSS a",
1468+
"dd-MM-yy hh:mm:ss a",
1469+
"dd-MM-yy hh:mm a",
1470+
"dd-MM-yy hh a",
1471+
"dd/MM/yy hh:mm:ss.SSS a",
1472+
"dd/MM/yy hh:mm:ss a",
1473+
"dd/MM/yy hh:mm a",
1474+
"dd/MM/yy hh a",
1475+
"dd/MM/yyyy hh:mm:ss.SSS a",
1476+
"dd/MM/yyyy hh:mm:ss a",
1477+
"dd/MM/yyyy hh:mm a",
1478+
"dd/MM/yyyy hh a",
1479+
"dd/MMM/yy hh:mm:ss.SSS a",
1480+
"dd/MMM/yy hh:mm:ss a",
1481+
"dd/MMM/yy hh:mm a",
1482+
"dd/MMM/yy hh a",
1483+
"dd/MMM/yyyy hh:mm:ss.SSS a",
1484+
"dd/MMM/yyyy hh:mm:ss a",
1485+
"dd/MMM/yyyy hh:mm a",
1486+
"dd/MMM/yyyy hh a",
1487+
"dd/MMMM/yy hh:mm:ss.SSS a",
1488+
"dd/MMMM/yy hh:mm:ss a",
1489+
"dd/MMMM/yy hh:mm a",
1490+
"dd/MMMM/yy hh a",
1491+
"dd/MMMM/yyyy hh:mm:ss.SSS a",
1492+
"dd/MMMM/yyyy hh:mm:ss a",
1493+
"dd/MMMM/yyyy hh:mm a",
1494+
"dd/MMMM/yyyy hh a",
1495+
]);
1496+
1497+
LABKEY.useMDYDateParsing = true;
1498+
});
1499+
1500+
});
1501+
11751502
});

0 commit comments

Comments
 (0)