Could you consider a tool to put a date into a specified format … so that, for example, selecting text such as "January 29, 2024” could be transformed to an international day-month-year format, such as “29 January 2024” or “29 Jan 2024” or “29.01.2024”, etc. You’d have to allow for a slew of possible input formats but fewer, I think, output formats.
Here is a snippet for this:
// #popclip
// name: DateFormatConverter
// icon: 📅
// description: Convert dates to a specified format.
// language: javascript
// Example function to convert date to the desired format
function convertDateToFormat(inputText) {
// Attempt to parse the date using built-in Date parsing
const parsedDate = new Date(inputText);
// Check if the date is valid
if (!isNaN(parsedDate)) {
// Define the output formats
const options1 = { day: '2-digit', month: 'long', year: 'numeric' }; // "29 January 2024"
const options2 = { day: '2-digit', month: 'short', year: 'numeric' }; // "29 Jan 2024"
const options3 = { day: '2-digit', month: '2-digit', year: 'numeric' }; // "29.01.2024"
// Choose the desired output format here by uncommenting one of the lines below
const outputFormat = new Intl.DateTimeFormat('en-GB', options1).format(parsedDate); // for "29 January 2024"
//const outputFormat = new Intl.DateTimeFormat('en-GB', options2).format(parsedDate); // for "29 Jan 2024"
//const outputFormat = new Intl.DateTimeFormat('en-GB', options3).format(parsedDate).replace(/\//g, '.'); // for "29.01.2024"
return outputFormat;
} else {
// If the date is not valid, return the input text
return "Invalid date format";
}
}
// Main PopClip action
popclip.pasteText(convertDateToFormat(popclip.input.text));
(The above block is an extension snippet — select it then click “Install Extension” in PopClip.)
I modified your example into a snippet (below) that toggles between using the current date vs selected text based on whether the selected text is only whitespace.
Unfortunately, PopClip doesn’t activate if only whitespace is selected. Is there away to activate my snippet with a selection of whitespace? Alternatively, do you have a better idea for signaling to simply insert the current date?
Thank you.
// #popclip
// name: YYYY-MM-DD
// icon: symbol:calendar
// description: Convert date to YYYY-MM-DD.
// language: javascript
function convertDateFormat(inputText) {
// function to convert format of date passed as inputText
//
//
var inputDate; // Date to convert to string formatted as YYY-MM-DD
// Check if inputText might be a date
if( inputText.trim().length === 0 ) {
// inputText is empty or all whitespace so format today's date
inputDate = new Date();
}
else {
// Attempt to parse inputText into a date
inputDate = new Date(inputText);
}
// Check if the inputDate is valid
if (isNaN(inputDate)) {
// Date is not valid, return the input text
return "'" + inputText + "' is not a valid date format";
}
else {
// Define the output format options
const option1 = { day: '2-digit', month: 'long', year: 'numeric' }; // "29 January 2024"
const option2 = { day: '2-digit', month: 'short', year: 'numeric' }; // "29 Jan 2024"
const option3 = { day: '2-digit', month: '2-digit', year: 'numeric' }; // "29 01 2024"
// Choose the desired output format here by uncommenting only one of the lines below
// const outputFormat = new Intl.DateTimeFormat('en-GB', option1).format( inputDate); // for "29 January 2024"
// const outputFormat = new Intl.DateTimeFormat('en-GB', option2).format( inputDate).replace(/\//g, '-'); // for "29-Jan-2024"
// const outputFormat = new Intl.DateTimeFormat('en-GB', option3).format( inputDate).replace(/\//g, '-'); // for "29-01-2024"
const outputFormat = inputDate.toISOString().split('T')[0]; // for "2024-01-29"
return outputFormat;
}
}
// Main PopClip action
popclip.pasteText(convertDateFormat(popclip.input.text));
PopClip won’t activate on a whitespace-only selection. However it will activate on an empty selection, by long pressing the mouse button (hold for 0.5 seconds). Alternatiely, Shift-click. Or use the PopClip keyboard shortcut to make it appear. Double-click will work in some text areas too.
In the extension config you’ll need to set requirements: [] to tell PopClip to show the extension’s actions when there is no selected text.
example
// #popclip - appear with "Paste" only (no selected text needed)
// name: paster
// requirements: []
// language: javascript
popclip.pasteText(`input text is '${popclip.input.text}'`)
One way to do this is to have more than one action in the extension. This can be done by making the extension module-based and exporting more than one action. See example below.
Another way is to react to a modifier key – Option is the bets choice – by checking popclip.modifiers.option.
I’ll write up an example with everything in.
// #popclip
// name: DateFormatConverter
// icon: 📅
// description: Convert dates to a specified format.
// requirements: []
// language: javascript
// module: true
const dateOptions1 = { day: '2-digit', month: 'short', year: 'numeric' }; // "29 Jan 2024"
const dateOptions2 = { day: '2-digit', month: '2-digit', year: 'numeric' }; // "29/01/2024"
// Example function to convert date to the desired format
function convertDateToFormat(inputText, dateOptions) {
// Attempt to parse the date using built-in Date parsing.
// If no input text, use current date.
const parsedDate = inputText.length > 0 ? new Date(inputText) : new Date();
// Check if the date is valid
if (!isNaN(parsedDate)) {
return Intl.DateTimeFormat('en-GB', dateOptions).format(parsedDate);
} else {
return "Invalid date format";
}
}
// an action for each format
exports.actions = [{
title: "DateFormat1",
icon: "d1", // unimaginative icon for now
code: () => popclip.pasteText(convertDateToFormat(popclip.input.text, dateOptions1))
},{
title: "DateFormat2",
icon: "d2",
code: () => popclip.pasteText(convertDateToFormat(popclip.input.text, dateOptions2))
}];
Alternative using Option key to differentiate:
// ... rest as above
// choose format based on presence of option modifier.
// here we don't need to wrap the code in an object with title and icon,
// since we only have one action so we are happy to just inherit
// the extension's name and icon. (this is basically what happens under
// the hood when using a simple javascript action without `module: true`)
exports.action = () => {
popclip.pasteText(convertDateToFormat(
popclip.input.text,
popclip.modifiers.option ? dateOptions2 : dateOptions1
));
}
My resulting Snippet (below) seems to work well. I’m very pleased with its coding structure because it can be easily extended to support more output formats simply by adding one of the following for each additional format:
DateFormat,
switch case, and
exports.options item.
I struggled mightily to create icons that communicated the idea of formatting a date as either ISO (YYYY-MM-DD) or Short Month (DD-MMM-YYYY). Ideally, my icons would have consisted of two parts, an icon for a calendar followed by a modifier that conveyed the format. Ultimately, I settled on just the format because I couldn’t figure out how to combine an icon such as symbol:calendar with text representing the format.
However, I’m pleased to learn that mousing over an icon reveals the title, as you can see in this screenshot:
Nice job! Love to usee user success. Icons look great to me, the main thing is that they convey recognisable meaning. It’s not possible to combine text and and image in the same icon, though I can imagine that might be a useful option.
I can post in more detail later but you want to look into the Options section.
Define a boolean option with identifier e.g. style1, style2 for each action and then in the action set a requirements array e.g. ["option-style1=1"] on the corresponding action.
I looked at the Options section but can’t quite understand exactly how to create the options because there is neither an example of an options array in general nor one for exported actions in particular.
Thanks, @nick and @nello. I do all dates in ISO format, so this is really useful for me! Greatly appreciated.
A question: The extension seems to work well with dates that have a day, month, and year. However, if a date has only a month and day, the extension defaults to the year 2000. Is there a way to make it so it defaults to the current year, which would be my probable use case for a date without a year?
This is a really good question. Easiest way would be to just detect dates set to 2000 and then replace with current year … but this would fail if the actual date was in 2000!
Need to think how to tell the two cases apart. I guess if the original link text contains the string 2000 we can assume it is actually 2000, otherwise we replace the year. I’ll have a think…
Apparently new Date(inputText.trim()) makes an assumption about a missing year only when inputText contains a month represented as text; numeric dates without a year are NOT a valid format:
For example, when I choose ISO:
Numeric month 6/8 → '6/8' is not a valid date format 6-18 → ‘6-16' is not a valid date format
Text Month June 18 → 2000-06-18 (but user wants 2024-06-18) jun 18 → 2000-06-18 (but user wants 2024-06-18)
The year 2000 can be represented with any number of zeros jun 18 0 → 2000-06-18 jun 18 00 → 2000-06-18 jun 18 000 → 2000-06-18 jun 18 0000 → 2000-06-18 jun 18 00000 → 2000-06-18
Year delimiters may be many but not all single-characters jun 18 2000 → 2000-06-18 jun 18-2000 → 2000-06-18 jun 18+2000 → 2000-06-18 jun 18=2000 → 'jun 18=2000' is not a valid date format
Most multi-character year delimiters will be trapped BUT NOT ALL! jun 18--2000 → 2001-06-18 (double-hyphen adds a year!?!) jun 18++0 → 'jun 18++0 ' is not a valid date format jun 18++2000 → 'jun 18++2000' is not a valid date format
So, apparently, the test for whether to change the year 2000 to the current year involves looking carefully at inputText.trim() and seeing whether if contains an “acceptable” — how many zeroes and what kinds of delimiters are valid vs typographical errors in the input? — representation of the year 2000.
If I were doing this, I’d add a new case to switch( dateOption ) so that year isn’t swapped without the user choosing to do so explicitly.