'Getting the start of the day in a different timezone

I have a Date. It is in the local timezone. I want a new Date that is at the beginning of the dayin a different timezone. Here are some things I do not want:

  • A Date in UTC equivalent to the first date converted to UTC
  • A string

Specifically, UTC does not work because getting the start of a day in UTC is not the same as getting the start of the day in a timezone.

So If I have a date in Calcutta and want to get the start of that day in San Francisco, the date in Calcutta and the date in Greenwich might not be the same date. It could be June 15th in Calcutta, June 15th in Greenwich, but June 2nd in San Francisco. So calling setMinutes(0) etc on a date that is set to UTC will not work.

I am also using date-fns (not moment) if that's helpful, but it doesn't seem to be because all dates (including those in date-fns-tz) are returned in either local or UTC time.)

Is this possible in Javascript or am I insane?

Note:

This is not the same as Convert date to another timezone in JavaScript

That is about converting to strings. I do not want strings.



Solution 1:[1]

One way is to:

  1. Get the current timezone offset at the required location
  2. Create a date for the required UTC date
  3. Apply the offset from #1

e.g. using the answer at Get Offset of the other Location in Javascript:

function getTimezoneOffset(date, loc) {
  let offset;
  ['en','fr'].some(lang => {
    let parts = new Intl.DateTimeFormat(lang, {
      minute: 'numeric',
      timeZone: loc,
      timeZoneName:'short'
    }).formatToParts(date);
    let tzName = parts.filter(part => part.type == 'timeZoneName' && part.value);
    if (/^(GMT|UTC)/.test(tzName[0].value)) {
      offset = tzName[0].value.replace(/GMT|UTC/,'') || '+0';
      return true;
    }
  });
  let sign = offset[0] == '\x2b'? '\x2b' : '\x2d';
  let [h, m] = offset.substring(1).split(':');
  return sign + h.padStart(2, '0') + ':' + (m || '00');
}

// Convert offset string in ±HH:mm to minutes
function offsetToMins(offset) {
  let sign = /^-/.test(offset)? -1 : 1;
  let [h, m] = offset.match(/\d\d/g);
  return sign * (h * 60 + Number(m));
}

// Format date as YYYY-MM-DD at loc
function formatYMD(loc, date) {
  return date.toLocaleDateString('en-CA',{timeZone:loc});
}

// Return stat of day for date at loc
function startOfDayAtLoc(loc, date = new Date()) {
  let offset = getTimezoneOffset(date, loc);
  let offMins  = offsetToMins(offset);
  let d = new Date(+date);
  d.setUTCHours(0, -offMins, 0, 0);
  // If date is + or - original date, adjust
  let oDateTS = formatYMD(loc, date);
  let sodDateTS = formatYMD(loc, d);
  if (sodDateTS > oDateTS) {
    d.setUTCDate(d.getUTCDate() - 1);
  } else if (sodDateTS < oDateTS) {
    d.setUTCDate(d.getUTCDate() + 1);
  }
  return d;
}

// Examples
// 1 June 2020 00:00:00 Z
let d = new Date(Date.UTC(2020, 5, 1));
['America/New_York',
 'Asia/Tokyo',
 'Pacific/Tongatapu',
 'Pacific/Rarotonga'
].forEach(loc => {
  let locD = startOfDayAtLoc(loc, d);
  console.log(loc + ' ' + getTimezoneOffset(d, loc) + 
  '\nZulu : ' + locD.toISOString() +
  '\nLocal: ' + locD.toLocaleString('en-GB',{
    timeZone: loc, timeZoneName:'long'
  }));
});
// QnD formatter
let f = (loc, d) => d.toLocaleString('en-CA', {
  timeZone: loc, 
  hour12:false
});
// Dates on different date to UTC date
let laDate = new Date('2022-04-30T18:00:00-07:00');
let la = 'America/Los_Angeles';
console.log(`${la} - ${f(la, laDate)}` +
`\nStart of day: ${f(la, startOfDayAtLoc(la, laDate))}`
);
let bneDate = new Date('2022-05-01T03:00:00+10:00');
let bne = 'Australia/Brisbane';
console.log(`${bne} - ${f(bne, bneDate)}` +
`\nStart of day: ${f(bne, startOfDayAtLoc(bne, bneDate))}`
);

However, I'd suggest you use a library with timezone support as there are many quirks with the Date object and there is a new Temporal object in the works.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1