Plug-In Forms: Date Input (Form.Field.Date)

Omni Automation Plug-Ins designed for applications like OmniFocus and OmniPlan often deal with dates and ranges of time, and so may request the user to identify specific dates and times. The Form.Field.Date class is used to create and display date input fields that enable the user to specify the parameters for the plug-in to process.

Constructor

Returns a new Date input field, optionally with an initial value. Custom date formatting for the field can be set using the Formatter.Date (Date Formatter) class (LINK).

Date Input Syntax

The value for the date input field can written using any syntax supported by an OmniOutliner date field. For example:

Basic Date Input

NOTE: This page contains interactive script examples that are executed only with your approval. See the section on Script Security for details about allowing the execution of remote scripts.
inputForm = new Form() dateInput = new Form.Field.Date( "indicatedDate", "Start Date", null, null ) formPrompt = "Enter a date:" buttonTitle = "Continue" inputForm.addField(dateInput) inputForm.show(formPrompt, buttonTitle)
Date Input: No Default Date, Default Formatting
  

dateInput = new Form.Field.Date( "indicatedDate", "Start Date", null, null )

(⬇ see below ) (macOS) Date input field with date picker showing

date-input-field

Providing a default value for the input:

tomorrow = new Date(new Date().setHours(24,0,0,0)) inputForm = new Form() dateInput = new Form.Field.Date( "indicatedDate", "Start Date", tomorrow, null ) formPrompt = "Enter a date after today:" buttonTitle = "Continue" inputForm.addField(dateInput) inputForm.show(formPrompt, buttonTitle)
Date Input: Provided Date (Tommorrow)
  

tomorrow = new Date(new Date().setHours(24,0,0,0)) dateInput = new Form.Field.Date( "indicatedDate", "Start Date", tomorrow, null )
date-input-field-with-provided-date

Custom date formatting for the field can be set using the Formatter.Date (Date Formatter) class (LINK).

fmtr = Formatter.Date.withStyle( Formatter.Date.Style.Long, Formatter.Date.Style.Short ) tomorrow = new Date(new Date().setHours(24,0,0,0)) inputForm = new Form() dateInput = new Form.Field.Date( "indicatedDate", "Start Date", tomorrow, fmtr ) formPrompt = "Enter a date after today:" buttonTitle = "Continue" inputForm.addField(dateInput) inputForm.show(formPrompt, buttonTitle)
Date Input: Provided Formatted Date
  

fmtr= Formatter.Date.withStyle( Formatter.Date.Style.Long, Formatter.Date.Style.Short ) tomorrow = new Date(new Date().setHours(24,0,0,0)) dateInput = new Form.Field.Date( "indicatedDate", "Start Date", tomorrow, fmtr )
date-input-field-with-provided-formatted-date

Date Validation Functions

Forms that contain date input fields usually implement checking functions in the form validation function to ensure that the date chosen by the user meets the parameters required by the plug-in processing.

NOTE: Date comparison functions can be found in the Date class documentation (LINK) and the Plug-In Library documentation (LINK).

function dateOccursNextWeek(dateToCheck){ fmatr = Formatter.Date.withStyle(Formatter.Date.Style.Short) weekStart = fmatr.dateFromString('next week') dc = new DateComponents() dc.day = 7 var followingWeek = Calendar.current.dateByAddingDateComponents(weekStart, dc) return (dateToCheck >= weekStart && dateToCheck < followingWeek) } inputForm = new Form() dateInput = new Form.Field.Date( "indicatedDate", "Date", null, null ) formPrompt = "Enter a date from next week:" buttonTitle = "Continue" inputForm.addField(dateInput) inputForm.show(formPrompt, buttonTitle) inputForm.validate = function(formObject){ dateToCheck = formObject.values["indicatedDate"] if (!dateToCheck){return false} if (!dateOccursNextWeek(dateToCheck)){ throw "Date must be in the next week" } return true }
Day from Next Week
  

function dateOccursNextWeek(dateToCheck){ fmatr = Formatter.Date.withStyle(Formatter.Date.Style.Short) weekStart = fmatr.dateFromString('next week') dc = new DateComponents() dc.day = 7 followingWeek = Calendar.current.dateByAddingDateComponents(weekStart, dc) return (dateToCheck >= weekStart && dateToCheck < followingWeek) } inputForm = new Form() dateInput = new Form.Field.Date( "indicatedDate", "Date", null, null ) formPrompt = "Enter a date from next week:" buttonTitle = "Continue" inputForm.addField(dateInput) inputForm.show(formPrompt, buttonTitle) inputForm.validate = function(formObject){ dateToCheck = formObject.values["indicatedDate"] if (!dateToCheck){return false} if (!dateOccursNextWeek(dateToCheck)){ throw "Date must be in the next week" } return true }

And here’s the same script placed within in a plug-in wrapper:

Plug-In: Enter Date from Next Week
  

/*{ "type": "action", "targets": ["omnifocus","omnigraffle","omniplan","omnioutliner"], "author": "Otto Automator", "identifier": "com.omni-automation.all.chosen-day-next-week", "version": "1.1", "description": "Prompts the user to Select a Date that occurs in the following week.", "label": "Enter Day Next Week", "shortLabel": "Enter Day Next Week", "paletteLabel": "Enter Day Next Week", "image": "calendar" }*/ (() => { function dateOccursNextWeek(dateToCheck){ fmatr = Formatter.Date.withStyle(Formatter.Date.Style.Short) weekStart = fmatr.dateFromString('next week') dc = new DateComponents() dc.day = 7 followingWeek = Calendar.current.dateByAddingDateComponents(weekStart, dc) return (dateToCheck >= weekStart && dateToCheck < followingWeek) } const action = new PlugIn.Action(async function(selection, sender){ inputForm = new Form() dateInput = new Form.Field.Date( "indicatedDate", "Date", null ) inputForm.addField(dateInput) inputForm.validate = function(formObject){ var dateToCheck = formObject.values["indicatedDate"] if (!dateToCheck){return false} if (!dateOccursNextWeek(dateToCheck)){ throw "Date must be in the next week" } return true } formPrompt = "Enter a date from next week:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt, buttonTitle) console.log(formObject.values["indicatedDate"]) }); action.validate = function(selection, sender){ return true }; return action; })();

Start and End Dates

Here’s an example presenting two date input fields for entering the start and end dates for an event. The validation function ensures that the entered end date is greater than the start date.

start-end-dates
(async () => { try { fmatr = Formatter.Date.withStyle(Formatter.Date.Style.Short) currentDateObject = fmatr.dateFromString('today') inputForm = new Form() startDateField = new Form.Field.Date( "startDate", "Start Date", currentDateObject ) endDateField = new Form.Field.Date( "endDate", "End Date", null ) inputForm.addField(startDateField) inputForm.addField(endDateField) inputForm.validate = function(formObject){ startDateObject = formObject.values["startDate"] if (!startDateObject){return false} startDateStatus = (startDateObject >= currentDateObject) if(!startDateStatus){throw "Start date must be midnight today or later"} endDateObject = formObject.values["endDate"] if (!endDateObject){return false} endDateStatus = (endDateObject > startDateObject) if(!endDateStatus){throw "End date must be greater than start date"} return (startDateStatus && endDateStatus) } formPrompt = "Enter the start and end dates:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt, buttonTitle) startDate = formObject.values['startDate'] endDate = formObject.values['endDate'] console.log("startDate", startDate) console.log("endDate", endDate) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
Start and End Dates
  

(async () => { try { fmatr = Formatter.Date.withStyle(Formatter.Date.Style.Short) currentDateObject = fmatr.dateFromString('today') inputForm = new Form() startDateField = new Form.Field.Date( "startDate", "Start Date", currentDateObject ) endDateField = new Form.Field.Date( "endDate", "End Date", null ) inputForm.addField(startDateField) inputForm.addField(endDateField) inputForm.validate = function(formObject){ startDateObject = formObject.values["startDate"] if (!startDateObject){return false} startDateStatus = (startDateObject >= currentDateObject) if(!startDateStatus){throw "Start date must be midnight today or later"} endDateObject = formObject.values["endDate"] if (!endDateObject){return false} endDateStatus = (endDateObject > startDateObject) if(!endDateStatus){throw "End date must be greater than start date"} return (startDateStatus && endDateStatus) } formPrompt = "Enter the start and end dates:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt, buttonTitle) startDate = formObject.values['startDate'] endDate = formObject.values['endDate'] console.log("startDate", startDate) console.log("endDate", endDate) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();

Choose Day from Next Week (menu)

As an alternative to using date input fields, forms can present dates to users in menus, as in this example for selecting a day in the upcoming week:

(⬇ see below ) The plug-in on macOS:

choose-day-from-next-week
(async () => { try { fmtr = Formatter.Date.withStyle(Formatter.Date.Style.Full) weekStart = fmtr.dateFromString("next week") dc = new DateComponents() menuItems = new Array() menuItems.push(fmtr.stringFromDate(weekStart)) var i; for (i = 1; i < 7; i++) { dc.day = i var date = Calendar.current.dateByAddingDateComponents(weekStart, dc) menuItems.push(fmtr.stringFromDate(date)) } menuIndexes = [0,1,2,3,4,5,6] menuElement = new Form.Field.Option( "menuElement", null, menuIndexes, menuItems, 0 ) inputForm = new Form() inputForm.addField(menuElement) inputForm.validate = function(formObject){ return true } formPrompt = "Choose a day from next week:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) menuIndex = formObject.values['menuElement'] chosenDateString = menuItems[menuIndex] chosenDate = fmtr.dateFromString(chosenDateString) console.log(chosenDate) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
Choose Day from Next Week
  

(async () => { try { fmtr = Formatter.Date.withStyle(Formatter.Date.Style.Full) weekStart = fmtr.dateFromString("next week") dc = new DateComponents() menuItems = new Array() menuItems.push(fmtr.stringFromDate(weekStart)) var i; for (i = 1; i < 7; i++) { dc.day = i var date = Calendar.current.dateByAddingDateComponents(weekStart, dc) menuItems.push(fmtr.stringFromDate(date)) } menuIndexes = [0,1,2,3,4,5,6] menuElement = new Form.Field.Option( "menuElement", null, menuIndexes, menuItems, 0 ) inputForm = new Form() inputForm.addField(menuElement) inputForm.validate = function(formObject){ return true } formPrompt = "Choose a day from next week:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) menuIndex = formObject.values['menuElement'] chosenDateString = menuItems[menuIndex] chosenDate = fmtr.dateFromString(chosenDateString) console.log(chosenDate) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();

(⬇ see below ) The plug-in on iPadOS:

date-day-from-next-week

Select a Date (menus)

In this second example of date input alternatives, the user can choose any date by selecting from a series of of dynamic follow-on menus:

(⬇ see below ) The plug-in on macOS:

select-date-from-menus
(async () => { try { locale = new Intl.Collator().resolvedOptions().locale.toLowerCase() monthNames = [] for (i = 0; i < 12; i++) { var objDate = new Date() objDate.setDate(1) objDate.setMonth(i) monthName = objDate.toLocaleString(locale,{month:"long"}) monthNames.push(monthName) } now = new Date() currentYear = now.getFullYear() yearNames = new Array() yearIndexes = new Array() for (i = 0; i < 4; i++) { yearNames.push(String(currentYear + i)) yearIndexes.push(i) } dayCount = new Date(currentYear, 1, 0).getDate() dayIndexes = new Array() dayIndexStrings = new Array() for (var i = 0; i < dayCount; i++){ dayIndexes.push(i) dayIndexStrings.push(String(i + 1)) } inputForm = new Form() yearMenu = new Form.Field.Option( "yearMenu", "Year", yearIndexes, yearNames, 0 ) currentYearIndex = 0 monthMenu = new Form.Field.Option( "monthMenu", "Month", [0,1,2,3,4,5,6,7,8,9,10,11], monthNames, 0 ) currentMonthIndex = 0 dayMenu = new Form.Field.Option( "dayMenu", "Day", dayIndexes, dayIndexStrings, 0 ) inputForm.addField(yearMenu) inputForm.addField(monthMenu) inputForm.addField(dayMenu) inputForm.validate = function(formObject){ yearMenuIndex = formObject.values["yearMenu"] chosenYear = parseInt(yearNames[yearMenuIndex]) monthMenuIndex = formObject.values["monthMenu"] if(monthMenuIndex != currentMonthIndex || yearMenuIndex != currentYearIndex){ inputForm.removeField(inputForm.fields[2]) currentMonthIndex = monthMenuIndex currentYearIndex = yearMenuIndex } if (formObject.fields.length == 2){ dayCount = new Date(chosenYear, monthMenuIndex + 1, 0).getDate() dayIndexes = new Array() dayIndexStrings = new Array() for (var i = 0; i < dayCount; i++){ dayIndexes.push(i) dayIndexStrings.push(String(i + 1)) } dayMenu = new Form.Field.Option( "dayMenu", "Day", dayIndexes, dayIndexStrings, 0 ) inputForm.addField(dayMenu) } return true } formPrompt = "Select the date:" buttonTitle = "OK" formObject = await inputForm.show(formPrompt, buttonTitle) yearMenuIndex = formObject.values["yearMenu"] yearName = yearNames[yearMenuIndex] monthMenuIndex = formObject.values["monthMenu"] monthName = monthNames[monthMenuIndex] dayMenuIndex = formObject.values["dayMenu"] dayIndexString = dayIndexStrings[dayMenuIndex] targetDate = new Date(monthName + " " + dayIndexString + " " + yearName) console.log(targetDate) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
Select a Date (menus)
  

(async () => { try { locale = new Intl.Collator().resolvedOptions().locale.toLowerCase() monthNames = [] for (i = 0; i < 12; i++) { var objDate = new Date() objDate.setDate(1) objDate.setMonth(i) monthName = objDate.toLocaleString(locale,{month:"long"}) monthNames.push(monthName) } now = new Date() currentYear = now.getFullYear() yearNames = new Array() yearIndexes = new Array() for (i = 0; i < 4; i++) { yearNames.push(String(currentYear + i)) yearIndexes.push(i) } dayCount = new Date(currentYear, 1, 0).getDate() dayIndexes = new Array() dayIndexStrings = new Array() for (var i = 0; i < dayCount; i++){ dayIndexes.push(i) dayIndexStrings.push(String(i + 1)) } inputForm = new Form() yearMenu = new Form.Field.Option( "yearMenu", "Year", yearIndexes, yearNames, 0 ) currentYearIndex = 0 monthMenu = new Form.Field.Option( "monthMenu", "Month", [0,1,2,3,4,5,6,7,8,9,10,11], monthNames, 0 ) currentMonthIndex = 0 dayMenu = new Form.Field.Option( "dayMenu", "Day", dayIndexes, dayIndexStrings, 0 ) inputForm.addField(yearMenu) inputForm.addField(monthMenu) inputForm.addField(dayMenu) inputForm.validate = function(formObject){ yearMenuIndex = formObject.values["yearMenu"] chosenYear = parseInt(yearNames[yearMenuIndex]) monthMenuIndex = formObject.values["monthMenu"] if(monthMenuIndex != currentMonthIndex || yearMenuIndex != currentYearIndex){ inputForm.removeField(inputForm.fields[2]) currentMonthIndex = monthMenuIndex currentYearIndex = yearMenuIndex } if (formObject.fields.length == 2){ dayCount = new Date(chosenYear, monthMenuIndex + 1, 0).getDate() dayIndexes = new Array() dayIndexStrings = new Array() for (var i = 0; i < dayCount; i++){ dayIndexes.push(i) dayIndexStrings.push(String(i + 1)) } dayMenu = new Form.Field.Option( "dayMenu", "Day", dayIndexes, dayIndexStrings, 0 ) inputForm.addField(dayMenu) } return true } formPrompt = "Select the date:" buttonTitle = "OK" formObject = await inputForm.show(formPrompt, buttonTitle) yearMenuIndex = formObject.values["yearMenu"] yearName = yearNames[yearMenuIndex] monthMenuIndex = formObject.values["monthMenu"] monthName = monthNames[monthMenuIndex] dayMenuIndex = formObject.values["dayMenu"] dayIndexString = dayIndexStrings[dayMenuIndex] targetDate = new Date(monthName + " " + dayIndexString + " " + yearName) console.log(targetDate) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();

(⬇ see below ) The plug-in on iPadOS:

choose-date-from-menus