Summary
This edit check interrogates Incomplete DateTime and Partial DateTime items to ensure that the entered timing components are logical.
Logic
This edit check will elicit an alert if a ‘lower level’ timing component is specified without the the full hierarchy of ‘higher level’ components also specified. For example:
Seconds are specified; but not Minutes, Hours, Day, Month, and Year
Minutes are specified; but not Hours, Day, Month, and Year
Hours are specified; but not Day, Month, and Year
Day is specified; but not Month and Year
Month is specified; but not Year
Formal Expression
function getTimeZoneOffset(timeZoneId) { var timezoneOffsets = { "US/Pacific": -7 * 60 * 60 * 1000, "US/Mountain": -6 * 60 * 60 * 1000, "US/Central": -5 * 60 * 60 * 1000, "US/Eastern": -4 * 60 * 60 * 1000, "Europe/London": 0 * 60 * 60 * 1000, "Europe/Berlin": 1 * 60 * 60 * 1000, "Europe/Paris": 1 * 60 * 60 * 1000, "Europe/Madrid": 1 * 60 * 60 * 1000, "Europe/Rome": 1 * 60 * 60 * 1000, "Europe/Warsaw": 1 * 60 * 60 * 1000, "Europe/Moscow": 3 * 60 * 60 * 1000, "UTC": 0 * 60 * 60 * 1000, "GMT": 0 * 60 * 60 * 1000, }; return timezoneOffsets[timeZoneId] || 0; } function normalizeIncompleteDatetime(value, timeZoneId) { logger("Original value: " + value); // Split the date and time parts var parts = value.split('T'); var datePart = parts[0].split('-'); // Default year, month, and day var year = datePart[0] ? parseInt(datePart[0], 10) : 0; var month = datePart[1] && datePart[1].trim() !== '' ? parseInt(datePart[1], 10) - 1 : 0; // Default to January (month = 0) var day = datePart[2] && datePart[2].trim() !== '' ? parseInt(datePart[2], 10) : 1; // Default to 1st of the month // Split the time part and pad missing parts with defaults var timePart = parts.length > 1 ? parts[1].split(':') : []; var hour = timePart[0] && timePart[0].trim() !== '' ? parseInt(timePart[0], 10) : 0; var minute = timePart[1] && timePart[1].trim() !== '' ? parseInt(timePart[1], 10) : 0; var second = timePart.length > 2 && timePart[2].trim() !== '' ? parseInt(timePart[2], 10) : 0; // Create the Date object directly in the local timezone var normalizedDatetime = new Date(year, month, day, hour, minute, second); // Check if the date is valid if (isNaN(normalizedDatetime.getTime())) { logger("Error: Invalid Date created from input value."); customErrorMessage('The datetime "' + value + '" could not be interpreted as a valid date.'); return null; } // Adjust the datetime according to the timezone offset var timezoneOffset = getTimeZoneOffset(timeZoneId); normalizedDatetime = new Date(normalizedDatetime.getTime() - timezoneOffset); // Corrected: Subtracting the offset to convert from MT to UTC logger("Date object adjusted for " + timeZoneId + ": " + normalizedDatetime); return normalizedDatetime; } function isNotInFuture(datetimeValue, timeZoneId) { logger("Starting isNotInFuture check..."); // Normalize the incomplete datetime value directly in the timezone var normalizedDatetime = normalizeIncompleteDatetime(datetimeValue, timeZoneId); if (!normalizedDatetime) { // Return false if we couldn't create a valid date return false; } logger("Normalized datetime for comparison: " + normalizedDatetime); // Get the current time adjusted to the local timezone var now = new Date(); logger("Current local time: " + now); // Compare the datetime if (normalizedDatetime.getTime() >= now.getTime()) { var errorMessage = 'The datetime "' + datetimeValue + '" is in the future or exactly at the current time, which is not allowed.'; logger("Error: " + errorMessage); customErrorMessage(errorMessage); return false; } logger("Datetime is not in the future."); return true; } // Extract the timezone from formJson var timeZoneId = formJson.form.subject.volunteer.site.timeZoneId; logger("TimeZone ID: " + timeZoneId); // Run the check against the provided item value logger("Item value: " + itemJson.item.value); var isValid = isNotInFuture(itemJson.item.value, timeZoneId); logger("Validation result: " + isValid); return isValid;