Time Zone Preview: a PopClip extension for calendar time ranges

I made a small PopClip extension to help with calendar invitations and event listings that use another time zone.

Select a string such as:

Wednesday, September 16 • 10am – 12pm PDT

Then choose the TZ action. It shows the start time converted to Melbourne (Australia) time, without replacing or altering the selected text.

For the example above, it shows:

3:00 am Melbourne time (AEST)

The extension currently recognises these common abbreviations:

PDT, PST, EDT, EST, CDT, CST, MDT, MST, BST, GMT, UTC,
AEST, AEDT, ACST, ACDT, and AWST.

It is deliberately preview-only: it uses PopClip’s show-result behaviour, so the original selected text remains unchanged.

Current limitations:

  • It converts the start of a time range, rather than displaying both start and end times.
  • It uses fixed offsets for abbreviations. That is usually fine for an explicitly stated daylight/standard abbreviation such as PDT or AEST, but it does not yet infer daylight-saving status from the calendar date.
  • Some abbreviations are globally ambiguous, particularly CST and IST.

I’m sharing it in case it is useful to others, and I’d welcome suggestions—especially on supported input formats, target-zone configuration, and a better icon/title.

Extension code follows:

// #popclip
// name: Time Zone Preview
// icon: TZ
// after: show-result
// language: javascript

const text = popclip.input.text.trim();
const match = text.match(
  /\\b(\\d{1,2})(?::(\\d{2}))?\\s\*(am|pm)\\s\*(?:–|-|to)\\s\*(\\d{1,2})(?::(\\d{2}))?\\s\*(am|pm)?\\s+(PDT|PST|EDT|EST|CDT|CST|MDT|MST|BST|GMT|UTC|AEST|AEDT|ACST|ACDT|AWST)\\b/i,
);

if (!match) {
  return "Select a time range such as ‘10am – 12pm PDT’.";
}

let [, hourText, minuteText, ampmText, , , , zoneText] = match;
let hour = Number(hourText);
const minute = Number(minuteText || 0);
const ampm = ampmText.toLowerCase();
const zone = zoneText.toUpperCase();

if (ampm === "pm" && hour !== 12) hour += 12;
if (ampm === "am" && hour === 12) hour = 0;

const offsets = {
  PDT: -7,
  PST: -8,
  EDT: -4,
  EST: -5,
  CDT: -5,
  CST: -6,
  MDT: -6,
  MST: -7,
  BST: 1,
  GMT: 0,
  UTC: 0,
  AEST: 10,
  AEDT: 11,
  ACST: 9.5,
  ACDT: 10.5,
  AWST: 8,
};

const sourceOffset = offsets[zone];
const melbourneOffset = 10;
const convertedMinutes =
  (hour * 60 + minute + (melbourneOffset - sourceOffset) * 60 + 1440) % 1440;
const convertedHour = Math.floor(convertedMinutes / 60);
const convertedMinute = convertedMinutes % 60;
const convertedAmPm = convertedHour >= 12 ? "pm" : "am";
const displayHour = convertedHour % 12 || 12;
const displayMinute = String(convertedMinute).padStart(2, "0");

return `${displayHour}:${displayMinute} ${convertedAmPm} Melbourne time (AEST)`;

Thanks @Peter_B !

The forum messed up the code because you didn’t use the code formatting option, so I tried to fix it :slight_smile:

Sorry, Nick, and thank you!

1 Like