Please Help Me With An Idiot's Guide to Converting UTC to Local Times

I’ve just spend half an hour reading posts about working with Local and UTC times in Wappler and I am just confused. Maybe I’m just being stupid and missing something obvious…

The Issue

My app is international. It stores the times when things happen. Hence I want to store the times in the database in UTC time, and display them in my app in the user’s local time.

Storing them in UTC time seems very easy. In my Database Insert action I just enter NOW_UTC in the appropriate field.

I would like to retrieve the UTC time from the database, and in my client side HTML just use some kind of function like .toLocalTime() to display them in the local time:

<p>{{query1.data.some_datetime.toLocalTime().formatDate('d MMM (h:mma)')}}</p>

Here are my questions:

  1. Does Wappler have this kind of function? If so, what is it?
  2. If not, should I work out my local time offset from two different datetime items set to local and UTC time and use that offset to do the conversion?
  3. If that isn’t possible, do I need to store the user’s local timezone in MySQL and do it from there? Will that need a stored procedure or something to work out the offset?

Please help me someone! :confused:

3 Likes

There’s several ways to do this…

One approach would be to have the user choose a timezone and store it as a preference against their record in the DB. If you store the +/- hours of the timezone, you can then use the .addHours() client-side formatter whenever displaying a date to the user.

You would always store times in UTC, as you are doing. Adjust using the user’s preference in forms - using a hidden field - if they are submitting the time to be stored.

This way if you have a user who lives in Europe, for example, but works for an American company, they could choose the timezone that suits them best.

An alternative, rather than the user having to set the timezone, would be to use something like https://ipstack.com/ to detect the user’s location when logging in - there is a timezone offset element in the API response that you could use to adjust with

You could also use a SC call to get the server time and compare it to the client’s time. If doing this, use a dynamic event when the SC that gets this this value succeeds (or on page load) to set a separate variable to the datetime value. If you just use the datetime element directly, it continues to change on every update tick so you don’t have a consistent offset against the server value that was called (and remains static). I have this method in use on a nodejs project where in the Global steps I have a servertime value set and output


and this on the page:

<body is="dmx-app" id="portalpage" dmx-on:ready="var_clientdatetime.setValue(datetime1.datetime)">
<dmx-datetime id="datetime1"></dmx-datetime>  
<dmx-value id="var_clientdatetime"></dmx-value>
  <dmx-value id="var_timeoffset" dmx-bind:value="var_clientdatetime.value.secondsUntil('<%=_('servertime',locals)%>')"></dmx-value>

The var_timeoffset value can then be used anywhere you wish and represents the second to be taken off the value to be displayed

For displaying datetime:
yourdatevalue.addSeconds(-var_timeoffset.value)
For hidden inputs if a use needs to choose a datetime and you want it converted to server time:
dmx-bind:value="yourdateinput.value.addSeconds(var_timeoffset.value)"

2 Likes

Thanks sooo much for that @bpj!

1 Like

So @bjp, can you help me with the last bit? I didn’t quite follow all the syntax in your code above… but what I have is this:

<dmx-datetime id="local_datetime"></dmx-datetime>
<dmx-value id="server_offset_seconds"></dmx-value>

Then I set the offset value when the user logs in:

server_offset_seconds.setValue(local_datetime.datetime.secondsUntil(f_login.data.server_time))

I’m getting valid time values for local_datetime and f_login.data.server_time, which I’ve tried with times one and two hours from UTC:

minus_1

But my value of server_offset_seconds is always -1, whereas it should be -60 or -120.

Am I doing something wrong here?

Mine doesn’t have the Z (zulu) time on the server_time value.
Have to got the .toUTC() formatter added on the server_time value? If you choose NOW_UTC you don’t need it. It should return the value like so: image

Thanks for the reply Ben! :slight_smile:

I just have a plain old NOW_UTC output…

server_time

I’m on PHP, sounds like you are on nodeJS? Maybe that is where the difference is…

Just set NOW_UTC.formatDate('yyyy-MM-dd HH:mm:ss') to leave off any timezone information

The other thing to check is that the SC is called (with the server datetime) before the setValue is used. Maybe return the server_time with the login and trigger the setValue as a success event of the login (if you aren’t already), just to make sure it’s all available when required.

Yeay, that works! :slight_smile:

works

Thanks sooo much Ben! :rocket:

2 Likes

Yes, my setValue is on success of the login server action… :innocent:

1 Like

Hi Antony,
This thread led me to correct something that I had noticed, wasn’t an issue for me yet but might be soon. When the time coming back from the server takes more than ½ second to return, the time difference might be 1 or more seconds away from the exact timezone. Can be seen in your example being -3601 rather than -3600

Most of the time this is OK and won’t be noticed by the user.

I have parts I’m developing where a user has to select a date & time before submitting a form and I am using a hidden field to send the value that has been corrected to the server UTC time. In this case a user may select 20:00:00 but end up with 19:59:59 or 20:00:01 being stored if it is out by 1 second.

To correct this you can use the following value:

<dmx-value id="var_timeoffset_corrected"
    dmx-bind:value="var_timeoffset.value % 60 ? var_timeoffset.value > 0 ? var_timeoffset.value + ( 60 - (var_timeoffset.value % 60).abs()) : var_timeoffset.value + ( (var_timeoffset.value % 60).abs()) : var_timeoffset.value">
  </dmx-value>

Explanation: It uses the modulus (%) function to get the remainder of the difference when divided by 60 and then either adds it on or takes it off as appropriate (depending on whether the timezone is + or - from UTC)

I hope it helps

Thanks for those thoughts Ben!

I only need one minute accuracy so the one second out was fine for me!

1 Like