UncleBob
UncleBob

Reputation: 1391

Interpret (NOT convert!) Date in other timezone in JavaScript/moment

This question seems similar to Deprecation warning: moment construction falls back to js Date, as the same warning appears. However, that warning is not the focus of this question, and the solution described is not well suited for my use case. I've edited the appropriate passages below to explain why this is so.

Consider a web application where the user can select a time range of images made by a stationary camera. Obviously, this camera may not be in his own timezone. Add to that that the REST-API delivering the images does everything in UTC.

I am using angular4, moment.js and moment timezone.

So he can choose whether he wants to see the images with timestamps in his local timezone, the timezone the camera is in, or in UTC. So far so well, everything is working up to this point.

Where I get in trouble is when setting the time range. Obviously, the start, and end times the user sets should be interpreted as being in the timezone he has selected for display, so I can convert them to UTC before sending the request to the server. The Date object I get from the date picker (primeng calendar in this case, but that doesn't really matter) is of course in browser local time.

So what I need to be able to do is take that Date and interpret it in the timezone of the camera, as is, without converting it. So if the user sets "from 10:00 to 17:00" while sitting in timezone UTC -2, and the camera is located in UTC + 2, I need to get "10:00 to 17:00 in UTC +2", NOT "14:00 to 21:00 in UTC +2".

I tried several approaches, some of which even work, but each seems to have their own caveats, and they're all butt-ugly because they do unnecessary string conversions. For example:

moment.tz(date.toLocaleString, cameraTimezone)

This works, but throws a deprecation warning:

value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions.

I could get rid of that warning by specifying what format the passed date string is in, but I'm facing the problem that neither toLocaleString() nor toString() are consistent. toLocaleString() will obviously give me another format depending on the locale, and toString() yields different results in different browsers, so how would I know what I get?

So I try with a proper format, but if I use Date::toIsoString directly, the moment I get has been converted to UTC, which is not what I need either:

let datestring = date.toISOString();   //datestring will be in utc
const mom = moment.tz(datestring, cameraTimezone);  //moment will therefore be the wrong time.

There's the obvious way of creating a string of the Date object myself (which is really ugly in javascript) and then parsing it into the moment by specifying that format. But all of these string conversions feel more like a hack than a proper solution, and I'd really like to get rid of them altogether and just tell moment "Here you have a date, it's supposed to be in this timezone, even if it says it isn't". Is there a way to do that?

Upvotes: 1

Views: 288

Answers (1)

RobG
RobG

Reputation: 147523

It may suit to construct an ISO 8601 compliant string based on the local date and user–selected time and timezone, e.g.

window.onload = function() {
  var form = document.getElementById('cameraTimeDetail');
  form.addEventListener('submit', getPicISO, false);
  
}

function getPicISO(e) {

  // For demo
  e.preventDefault();

  function z(n){return ('0'+n).slice(-2)}
  var date = new Date();
  var form = this;
  
  // Use local date parts
  var s = date.getFullYear() + '-' +
          z(date.getMonth()+1) + '-' +
          z(date.getDate()) + 'T' +
          // Use input timezone and time
          form.pictureTime.value + 
          form.cameraTimezone.value;

  // Consructed ISO string
  form.timeString.value = (s);
  
  // Convert to UTC ISO String
  form.timeStringUTC.value = new Date(s).toISOString();

  return false;
}
input, button {
  width: 15em;
}
<form id="cameraTimeDetail">
  <table>
    <tr>
      <td>Enter timezone (e.g. -08:00, +05:30)
      <td><input name="cameraTimezone" value="+05:30">
    <tr>
      <td>Enter time (HH:mm)
      <td><input name="pictureTime" value="14:37">
    <tr>
      <td><input type="reset">
      <td><button>Get pic</button>
    <tr>
      <td>Selected date and time:
      <td><input name="timeString" readonly>
    <tr>
      <td>Equivalent UTC:
      <td><input name="timeStringUTC" readonly>
  </table>
</form>

Obviously that is simplified and needs validation of input values but shows the logic. You may also want to let the user select the date, which can use the same logic.

Upvotes: 1

Related Questions