Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
25b5662
WEB-4330 add tolerance of 15 minutes to account for clock drift
henry-tp Jan 26, 2026
327428d
WEB-4330 update tests
henry-tp Jan 26, 2026
eb50f39
WEB-4330 update test descriptions
henry-tp Jan 26, 2026
2fe6353
v1.55.0-web-4330-missing-settings.1
henry-tp Jan 26, 2026
c55e633
WEB-4330 update comment spelling
henry-tp Jan 28, 2026
7e1d542
Merge branch 'WEB-4330-missing-settings' of https://github.com/tidepo…
henry-tp Jan 28, 2026
51f7498
WEB-4330 use MS_IN_MIN instead of magic number
henry-tp Jan 28, 2026
e540a74
Merge branch 'WEB-4330-missing-settings' of https://github.com/tidepo…
henry-tp Jan 28, 2026
1d668cf
v1.55.0-web-4330-missing-settings.2
henry-tp Jan 28, 2026
cf114bb
Merge pull request #587 from tidepool-org/WEB-4330-missing-settings
henry-tp Jan 28, 2026
3b9bcb6
WEB-4384 create new function for formatting with date + time
henry-tp Feb 5, 2026
f984e8d
Merge branch 'develop' into release-1.54.0
clintonium-119 Feb 5, 2026
041ffa6
Merge pull request #582 from tidepool-org/release-1.54.0
clintonium-119 Feb 5, 2026
f21345e
WEB-4384 fix test
henry-tp Feb 5, 2026
7ba989c
WEB-4384 add test cases for new format fn
henry-tp Feb 5, 2026
47bbc51
v1.55.0-web-4384-date-mismatch.1
henry-tp Feb 5, 2026
2de539d
WEB-4384 leave document dates untouched for BgLog
henry-tp Feb 6, 2026
a3b0018
v1.55.0-web-4384-date-mismatch.2
henry-tp Feb 6, 2026
b54f671
WEB-4384 move getChartDateBoundFormat logic into viz
henry-tp Feb 8, 2026
ceed6a0
WEB-4384 fix tests
henry-tp Feb 9, 2026
e8ba399
WEB-4384 fix lint
henry-tp Feb 9, 2026
e9e9465
v1.55.0-web-4384-date-mismatch.3
henry-tp Feb 9, 2026
adbec6c
Merge branch 'develop' into WEB-4384-date-mismatch
henry-tp Feb 10, 2026
7daa0bc
WEB-4384 fix not using standardized value
henry-tp Feb 10, 2026
6d56228
WEB-4384 improve comment
henry-tp Feb 10, 2026
559fe0e
v1.55.0-web-4384-date-mismatch.4
henry-tp Feb 10, 2026
62e6456
WEB-4384 wrap "Reporting Period" in t() call
henry-tp Feb 11, 2026
ddf81a0
v1.55.0-web-4384-date-mismatch.5
henry-tp Feb 11, 2026
ef4917e
Merge pull request #592 from tidepool-org/WEB-4384-date-mismatch
henry-tp Feb 11, 2026
f29cd19
v1.55.0-rc.1
krystophv Feb 18, 2026
dabe132
v1.55.0
henry-tp Mar 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"node": "20.8.0"
},
"packageManager": "yarn@3.6.4",
"version": "1.54.0",
"version": "1.55.0",
"description": "Tidepool data visualization for diabetes device data.",
"keywords": [
"data visualization"
Expand Down
4 changes: 4 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ import {
getLocalizedCeiling,
getOffset,
getTimezoneFromTimePrefs,
getChartDateBoundFormat,
CHART_DATE_BOUND_FORMAT,
} from './utils/datetime';

import { deviceName } from './utils/settings/data';
Expand Down Expand Up @@ -157,6 +159,8 @@ const utils = {
getLocalizedCeiling,
getOffset,
getTimezoneFromTimePrefs,
getChartDateBoundFormat,
CHART_DATE_BOUND_FORMAT,
},
stat: {
bankersRound,
Expand Down
7 changes: 6 additions & 1 deletion src/utils/DataUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -1480,9 +1480,14 @@ export class DataUtil {
ps => ps.time <= latestPumpData.time
);
} else {
// Clock drift on user's device may cause pumpSettings datum to have LATER timestamp than upload
// datum. The maximum time deviation that the Uploader allows between the user's device and Tidepool
// server time is 15 minutes, so we search up to 15 minutes into the future for the pumpSettings datum
const UPLOADER_TIME_DEVIATION_TOLERANCE = 15 * MS_IN_MIN;

pumpSettingsForUpload = _.filter(
pumpSettingsForUpload,
ps => ps.time <= latestPumpUpload.time
ps => ps.time <= (latestPumpUpload.time + UPLOADER_TIME_DEVIATION_TOLERANCE)
);
Comment on lines +1483 to 1491
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clock-drift tolerance is only applied when filtering pumpSettingsForUpload from pumpSettingsDatumsByIdMap, but the later fallback to this.latestDatumByType.pumpSettings still requires candidate.time <= latestPumpUpload.time for non-continuous uploads. This can still exclude a valid pumpSettings datum that’s within the 15-minute tolerance window when the by-id map doesn’t yield a match. Consider reusing the same tolerance check for the fallback path as well (and ideally hoist the tolerance constant so it’s not duplicated).

Copilot uses AI. Check for mistakes.
}

Expand Down
2 changes: 1 addition & 1 deletion src/utils/basics/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ export function basicsText(patient, data, stats, aggregations, copyAsTextMetadat
const textUtil = new utils.TextUtil(patient, endpoints.range, timePrefs, copyAsTextMetadata);

let basicsString = textUtil.buildDocumentHeader('Basics');
basicsString += textUtil.buildDocumentDates();
basicsString += textUtil.buildDocumentDates({ showPartialDates: true });

basicsString += textUtil.buildTextLine(t('Days with no insulin data have been excluded from calculations'));

Expand Down
34 changes: 33 additions & 1 deletion src/utils/datetime.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,9 @@ export function formatDiagnosisDate(patient) {
* formatDateRange
* @param {String|Date} startDate - A moment-compatible date object or string
* @param {String|Date} endDate - A moment-compatible date object or string
* @param {String} format - Optional. The moment format string to parse startDate and endDate with
* @param {String} dateParseFormat - Optional. The moment format string to parse startDate and endDate with
*
* @return {String} formatted date range for display
*/
export function formatDateRange(startDate, endDate, dateParseFormat, monthFormat = 'MMM') {
const start = moment.utc(startDate, dateParseFormat);
Expand All @@ -182,6 +184,36 @@ export function formatDateRange(startDate, endDate, dateParseFormat, monthFormat
return formattedRange;
}

export const CHART_DATE_BOUND_FORMAT = {
DATE_AND_TIME: 'MMM D, YYYY (h:mm A)',
DATE_ONLY: 'MMM D, YYYY',
};

/**
* getChartDateBoundFormat
* @param {Object} startDate - a moment time object
* @param {Object} endDate - a moment time object
*
* @return {String} a moment time format (e.g 'MMM D, YYYY')
*/
export function getChartDateBoundFormat(startDate, endDate) {
if (!endDate) return CHART_DATE_BOUND_FORMAT.DATE_ONLY;

const isStartDateMidnight = (startDate?.hours() === 0 && startDate?.minutes() === 0) ||
(startDate?.hours() === 23 && startDate?.minutes() >= 59);

const isEndDateMidnight = (endDate?.hours() === 0 && endDate?.minutes() === 0) ||
(endDate?.hours() === 23 && endDate?.minutes() >= 59);

const isMatchingDateBounds = isStartDateMidnight && isEndDateMidnight;

if (!isMatchingDateBounds) {
return CHART_DATE_BOUND_FORMAT.DATE_AND_TIME;
}

return CHART_DATE_BOUND_FORMAT.DATE_ONLY;
}
Comment on lines +187 to +215
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/utils/datetime.js has an in-file guideline to keep utilities organized alphabetically, but the newly added CHART_DATE_BOUND_FORMAT/getChartDateBoundFormat appear inserted here after formatDateRange, which breaks that convention. Please relocate these exports to match the file’s stated ordering so future diffs remain easy to scan.

Copilot uses AI. Check for mistakes.

/**
* formatDuration
* @param {Number} duration - positive integer duration in milliseconds
Expand Down
33 changes: 22 additions & 11 deletions src/utils/text/TextUtil.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import _ from 'lodash';
import table from 'text-table';
import i18next from 'i18next';
import moment from 'moment-timezone';

import {
formatBirthdate,
formatCurrentDate,
formatDateRange,
formatDiagnosisDate,
getOffset,
getTimezoneFromTimePrefs,
getChartDateBoundFormat,
CHART_DATE_BOUND_FORMAT,
} from '../datetime';

import { MS_IN_MIN } from '../constants';

import { getPatientFullName } from '../misc';

const t = i18next.t.bind(i18next);
Expand Down Expand Up @@ -60,17 +59,29 @@ export class TextUtil {
);
};

buildDocumentDates = () => {
buildDocumentDates = (opts = {}) => {
const { showPartialDates = false } = opts;

const timezone = getTimezoneFromTimePrefs(this.timePrefs);

// endpoint is exclusive, so need to subtract a millisecond from formatted range end date
let start = this.endpoints[0];
let end = this.endpoints[1] - 1;
const [start, end] = this.endpoints;
const startDate = moment.utc(start).tz(timezone);
const endDate = moment.utc(end).tz(timezone);

const dtMask = showPartialDates
? getChartDateBoundFormat(startDate, endDate)
: CHART_DATE_BOUND_FORMAT.DATE_ONLY;

// When showing dates only, we subtract 1ms from the end date because the end timestamp
// represents midnight of the following day, but we want to display the last included day.
// E.g. Jan 10 (12:00 AM) - Jan 20 (12:00 AM) should render as Jan 10 - Jan 19
if (dtMask === CHART_DATE_BOUND_FORMAT.DATE_ONLY) {
endDate.subtract(1, 'ms');
}

start = start - getOffset(start, timezone) * MS_IN_MIN;
end = end - getOffset(end, timezone) * MS_IN_MIN;
const formattedRange = `${startDate.format(dtMask)} - ${endDate.format(dtMask)}`;

return `\nReporting Period: ${formatDateRange(start, end)}\n`;
return `\n${t('Reporting Period')}: ${formattedRange}\n`;
};

buildTextLine = (text = '') => (_.isPlainObject(text) ? `${text.label}: ${text.value}\n` : `${text}\n`);
Expand Down
2 changes: 1 addition & 1 deletion src/utils/trends/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export function trendsText(patient, data, stats, chartPrefs, copyAsTextMetadata)
const textUtil = new utils.TextUtil(patient, endpoints.range, timePrefs, copyAsTextMetadata);
let trendsString = textUtil.buildDocumentHeader('Trends');

trendsString += textUtil.buildDocumentDates();
trendsString += textUtil.buildDocumentDates({ showPartialDates: true });

const excludedDays = _.map(_.keys(_.pickBy(chartPrefs.activeDays, day => day === false)), _.capitalize).join(', ');
if (excludedDays.length) trendsString += textUtil.buildTextLine({ label: 'Excluded Days', value: excludedDays });
Expand Down
51 changes: 49 additions & 2 deletions test/utils/DataUtil.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3746,7 +3746,7 @@ describe('DataUtil', () => {
expect(dataUtil.latestPumpUpload.settings.uploadId).to.equal('upload-A');
});

it('should not select pumpSettings more recent than the latest pump data used to determine the upload', () => {
it('should not select pumpSettings with timestamps more than 15 minutes later than the latest upload', () => {
const uploadA = { ...uploadData[0], uploadId: 'upload-A' };

const basal = {
Expand All @@ -3765,7 +3765,7 @@ describe('DataUtil', () => {
const pumpSettingsNew = {
type: 'pumpSettings',
uploadId: 'upload-A',
time: basal.time + 1000, // newer than basal
time: basal.time + (16 * MS_IN_MIN), // 16 minutes
id: 'ps-new',
};

Expand Down Expand Up @@ -3793,6 +3793,53 @@ describe('DataUtil', () => {
expect(dataUtil.latestPumpUpload.settings.id).to.equal('ps-old');
});

it('should select pumpSettings with timestamps up to 15 minutes later than the latest upload', () => {
const uploadA = { ...uploadData[0], uploadId: 'upload-A' };

const basal = {
type: 'basal',
time: Date.parse(uploadA.deviceTime) + 1000,
uploadId: 'upload-A',
};

const pumpSettingsOld = {
type: 'pumpSettings',
uploadId: 'upload-A',
time: basal.time - 1000,
id: 'ps-old',
};

const pumpSettingsNew = {
type: 'pumpSettings',
uploadId: 'upload-A',
time: basal.time + (12 * MS_IN_MIN), // 12 minutes
id: 'ps-new',
};

initDataUtil([
uploadA,
basal,
pumpSettingsOld,
pumpSettingsNew,
]);

dataUtil.pumpSettingsDatumsByIdMap = {
[pumpSettingsOld.id]: pumpSettingsOld,
[pumpSettingsNew.id]: pumpSettingsNew,
};

dataUtil.latestDatumByType = {
...dataUtil.latestDatumByType,
basal,
};

dataUtil.setLatestPumpUpload();

expect(dataUtil.latestPumpUpload).to.be.an('object');
expect(dataUtil.latestPumpUpload.settings).to.be.an('object');
expect(dataUtil.latestPumpUpload.settings.id).to.equal('ps-new');
});

it('should fall back to traditional pump upload logic when no pumpSettings data is available', () => {
// Clear any existing pumpSettings data
delete dataUtil.latestDatumByType.pumpSettings;
Expand Down
28 changes: 26 additions & 2 deletions test/utils/text/TextUtil.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,33 @@ describe('TextUtil', () => {
});

describe('buildDocumentDates', () => {
it('should print the document reporting period', () => {
it('should print the dates only when bounds are midnight', () => {
const result = textUtil.buildDocumentDates({ showPartialDates: true });

expect(result).to.equal('\nReporting Period: Feb 1, 2019 - Feb 19, 2019\n');
});

it('should print the dates and times when bounds are not midnight', () => {
const testEndpoints = [
Date.parse('2019-02-01T08:00:00.000Z'), // Feb 01 @ 3 AM
Date.parse('2019-02-20T08:00:00.000Z'), // Feb 20 @ 3 AM
];
textUtil = new TextUtil(patient, testEndpoints, timePrefs, copyAsTextMetadata);

const result = textUtil.buildDocumentDates({ showPartialDates: true });

expect(result).to.equal('\nReporting Period: Feb 1, 2019 (3:00 AM) - Feb 20, 2019 (3:00 AM)\n');
});

it('should print the document reporting period (with inclusive end date)', () => {
const testEndpoints = [
Date.parse('2019-02-01T08:00:00.000Z'), // Feb 01 @ 3 AM
Date.parse('2019-02-20T08:00:00.000Z'), // Feb 20 @ 3 AM
];
textUtil = new TextUtil(patient, testEndpoints, timePrefs, copyAsTextMetadata);
const result = textUtil.buildDocumentDates();
expect(result).to.equal('\nReporting Period: Feb 1 - Feb 19, 2019\n');

expect(result).to.equal('\nReporting Period: Feb 1, 2019 - Feb 20, 2019\n');
});
});

Expand Down