Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(fix): format observation datetime to accurate local time to prevent inaccurate timestamps #2208

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

amosmachora
Copy link
Contributor

Requirements

  • This PR has a title that briefly describes the work done including the ticket number. If there is a ticket, make sure your PR title includes a conventional commit label. See existing PR titles for inspiration.
  • My work conforms to the OpenMRS 3.0 Styleguide and design documentation.
  • My work includes tests or is validated by existing tests.

Summary

This PR formats the test timestamp in the results panel using the openmrs framework formatDatetime function that accurately handles the users locale. Since the date we had was already formatted to a certain format we first convert it to an ISO string (which was the original format) then format it correctly to the users local time removing the inconsistency.

Screenshots

Results

image

Visits

image

Related Issue

https://openmrs.atlassian.net/browse/O3-4297

Other

test?.obs?.map((entry) => {
const isoFormattedString = entry.obsDatetime.replace(' ', 'T').replace('.0', '.000').concat('Z');
const date = new Date(isoFormattedString);
return format(date, 'yyyy-MM-dd HH:mm:ss');
Copy link
Member

Choose a reason for hiding this comment

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

Why not use the framework formatDatetime here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did not want to break the existing functionality. In my investigation using formatDatetime returns dates like Today 06:50 am which was not my intention. I wanted to return a string like this 2024-09-04 06:23:23.0 so that wherever the value is being used it doesn't break anything. In short I wanted to return the original date but formatted correctly,

Choose a reason for hiding this comment

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

I vote for consistency across the system and I vote for the formatters provided by the framework. Is there a strong reason not to go with the formatted date from the framework's function?

Copy link
Contributor Author

@amosmachora amosmachora Jan 22, 2025

Choose a reason for hiding this comment

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

Yes @ojwanganto that date object at that point is not for display but rather to make sure it is in the correct timezone and that approach is what made it possible. using formatDateTime or any other format function from the framework did not quite fit the use case.

I am open to suggestions though.

Copy link
Member

Choose a reason for hiding this comment

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

I think it's fine to operate on time objects, but please use dayjs(), which supports everything you're trying to do here in a much cleaner way and is already part of the framework.

Comment on lines 120 to 123
const formatObsDatetime = (obsDatetime: string) => {
const isoFormattedString = obsDatetime.replace(' ', 'T').replace('.0', '.000').concat('Z');
return isoFormattedString;
};
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
const formatObsDatetime = (obsDatetime: string) => {
const isoFormattedString = obsDatetime.replace(' ', 'T').replace('.0', '.000').concat('Z');
return isoFormattedString;
};
const formatObsDatetime = (obsDatetime: string) => {
return dayjs(obsDatetime).toISOString();
};

dayjs already provides this conversion to ISO format.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The obsDatetime parameter is weird. It's an already formatted date string like this: 2025-01-22 05:28:00.0 Following your suggestion rolls back the date by 3 hours (probably because I am on EAT).

obsDatetime - 2025-01-22 05:28:00.0
isoFormattedString - 2025-01-22T05:28:00.000Z 
formattedWithDayJS - 2025-01-22T02:28:00.000Z

thanks for highlighting though I have seen a use case for it on the tooltip for accuracy purposes

Copy link
Member

Choose a reason for hiding this comment

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

So, yes, because dayjs interprets it as a local timestamp (which is what that format claims it is). Instead, you're adding an adjustment so this is treated as if it were in UTC, which is not something we should be deciding here, but on the backend.

Copy link
Member

Choose a reason for hiding this comment

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

Agree with @ibacher ... I'm forgetting, when does the server return something like "2025-01-22 05:28:00.0" (ie without specifying time zone).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mogoodrich highly likely that it does not. This is a modification that is most certainly done at the front end, somewhere up the "callstack".

Copy link
Member

Choose a reason for hiding this comment

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

We should certainly consider fixing that @amosmachora @denniskigen , where does the obstree resource live?

Copy link
Member

@denniskigen denniskigen Mar 3, 2025

Choose a reason for hiding this comment

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

I'm referring to this guy in the REST-WS module. And this is not in response to Amos' comments from above, but rather about this ticket in general. Per Ian, this specific issue is just another pointer to the known wider issue of how dates and time (specifically timezones) are handled in the backend.

Copy link
Member

@mogoodrich mogoodrich Mar 3, 2025

Choose a reason for hiding this comment

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

thanks @denniskigen !

@ibacher @dkayiwa @mseaton I'm thinking we want to fix this? specifically, when generating the value_datetime and obs_datetime here:

https://github.com/openmrs/openmrs-module-webservices.rest/blob/master/omod-1.9/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/resource/openmrs1_9/ObsTreeResource1_9.java#L153

... just format in the standard ISO string format instead?

Copy link
Member

Choose a reason for hiding this comment

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

@mogoodrich Yep. We do need to make allowance for, e.g., implementations that have been storing time in UTC rather than local time. But, yeah, ideally, all conversations through the API are done in ISO timestamps and the ambiguity should mostly go away.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks @ibacher ... isn't that even more of a reason to include an ISO string here? Stripping off the timezone seems to assume that the server time zone is the facility timezone, and we just want to display that time to the user, regardless of what timezone their client is in. (We've done that in the past with things like this: https://github.com/openmrs/openmrs-module-uicommons/blob/master/omod/src/main/webapp/resources/scripts/filters/serverDate.js but this has become problematic when the server is in UTC or some timezone that don't correspond to the clinic timezone)

@denniskigen denniskigen requested a review from ibacher January 29, 2025 13:40
test?.obs?.map((entry) => {
const isoFormattedString = entry.obsDatetime.replace(' ', 'T').replace('.0', '.000').concat('Z');
const date = new Date(isoFormattedString);
return format(date, 'yyyy-MM-dd HH:mm:ss');
Copy link
Member

Choose a reason for hiding this comment

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

I think it's fine to operate on time objects, but please use dayjs(), which supports everything you're trying to do here in a much cleaner way and is already part of the framework.

Comment on lines 120 to 123
const formatObsDatetime = (obsDatetime: string) => {
const isoFormattedString = obsDatetime.replace(' ', 'T').replace('.0', '.000').concat('Z');
return isoFormattedString;
};
Copy link
Member

Choose a reason for hiding this comment

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

So, yes, because dayjs interprets it as a local timestamp (which is what that format claims it is). Instead, you're adding an adjustment so this is treated as if it were in UTC, which is not something we should be deciding here, but on the backend.

@denniskigen denniskigen self-requested a review February 26, 2025 11:55
@denniskigen
Copy link
Member

@amosmachora did you see Ian's latest comments on the ticket?

@amosmachora
Copy link
Contributor Author

@amosmachora did you see Ian's latest comments on the ticket?

Yes I did Dennis, not gonna lie worked out perfectly for me because I had no clue how to rewrite the code with the dayjs implementation that Ian was suggesting : )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants