Current Date and Time

Scripting Solutions

Additional scripting solutions will be added in the future. Please reach out to Alchemer with comments and suggestions on solutions you'd like to see via the link here.

Goal 

Save the current date and/or time in a specific timezone for a response being collected in Alchemer.

Effort:   

Solution 

The current date and time the page is presented will be saved to one or more Hidden Value Actions.  The title of the Hidden Value Action determines what will be saved.  An additional Hidden Value Action contains a setup string to choose the timezone and additional settings.

 
   Step 1:  Add elements to save the date/time values   

Add one or more Hidden Value Actions with the following titles to save the current date or time in various formats.  The title of the Hidden Value Action determines what will be saved in that Action:

Hidden Value Action TitleExample value Description
current-date12/09/2020Save the date in the format defined below
current-time14:47:52Save the time in the format defined below
current-hour16The current hour, 0-23
current-day-of-weekTThe current day of the week: UMTWRFS
current-timestamp12/09/2020 14:47:52Save both the date and time
current-filterable-timestamp20201209144752Save a format that can be used in Alchmer reports to filter for a range of dates and times using <= and >= comparisons
current-iso-timestamp2020-12-09T15:47:52.248ZSave timestamp in the ISO format
current-milliseconds1607528920248Save current time in milliseconds since 01-01-1970, useful for Javascript
current-day-of-week
WedSave current day of week in short format specific to the Locale in the setup in Step 2
current-hour
14Save current hour, 1-23


   Step 2:   Set the format to save the date/time   

Add a Hidden Value Action with the title current-setup to define the timezone and basic format.  Enter your setup string in the "Populate with the following" field separating entries with vertical bars, for example:  America/Los_Angeles | en-US | 24h | set-once

      

Example settingAll Settings
America/Los_AngelesWorldwide timezone, see: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
en-USThe locale (date format, like EU dd/mm vs US mm/dd), see: https://en.wikipedia.org/wiki/Language_localisation
Note:  en-CA format is yyyy/mm/dd
24h12h for am/pm
24h for EU / US military time
set-onceset-once to only set the first time the page is displayed
set-always to reset each time the page is displayed.


Example page:


   Step 3:   Add a new Javascript Action   

Paste the below script into a new Javascript Action:

/* Alchemer, v02

   Save the current date and time as a specific timezone

   Documentation and updates: https://help.alchemer.com/help/current-date-and-time
*/

document.addEventListener("DOMContentLoaded", function() {

  // *******************************
  // *** no changes needed below ***
  // ******************************* */

  // Hidden Value Action titles to set (if they exist on the page)
  const DATE_TITLE                 = 'current-date'
  const TIME_TITLE                 = 'current-time'
  const TIMESTAMP_TITLE            = 'current-timestamp'
  const FILTERABLE_TIMESTAMP_TITLE = 'current-filterable-timestamp'
  const ISO_TIMESTAMP_TITLE        = 'current-iso-timestamp'
  const MILLISECONDS_TITLE         = 'current-milliseconds'
  const DOW_TITLE                  = 'current-day-of-week'
  const HOUR_TITLE                 = 'current-hour'

  const SETUP_TITLE                = 'current-setup'

  /***
  * Test boolean value, alert() and throw Error if it's false
  *
  * bool (t/f) value to test
  * msg (string) message to alert and throw in new Error
  */
  const assert = (bool, msg) => {
    msg = "Javascript Error:\n\n" + msg
    if (!bool) {
      alert(msg)
      console.error(msg)
      const err = new Error(msg)
      console.error(err)
      throw err
      }
  }

  /***
   * Get an element based on its Question ID
   *
   * qid (int/string) question ID
   * section = "element" (string) the final section of the element id
   * return (element) looks for id's in the form: "sgE-1234567-12-123-element"
   */
  const getElemByQid = (qid, section = "element") => {
    const id = "sgE-" + SGAPI.survey.surveyObject.id + "-" + SGAPI.survey.pageId + "-" + qid + "-" + section
    const elem = document.getElementById(id)
    assert(elem, "Javascript: can't find element with id = " + id + ", section = " + section)
    return elem
  }

  /***
   * Parse the Hidden Value titled SETUP_TITLE
   *
   * (return) setup object, example:
   *        {
   *          timezone: "America/Los_Angeles",
   *          locale:   "en-US",
   *          hours:    "24h",        
   *          set:      "set-once"
   *        }
   */
  const parseSetup = () => {
    const qid = getQidByTitle(SETUP_TITLE)
    assert(qid, `Missing element with title '${SETUP_TITLE}'`)
    const setupString = getElemByQid(qid).value

    const aParsed = setupString.match(/(.+)\|(.+)\|(.+)\|(.+)/)

    console.log("aParsed = \n", aParsed)
    assert(aParsed && aParsed.length === 5, "The current-setup string is not in expected format:\n\n'America/Los_Angeles | en-US | 24h | set-once'")

    const setup = {
      timezone: aParsed[1].trim(),
      locale:   aParsed[2].trim(),
      hours:    aParsed[3].trim(),
      set:      aParsed[4].trim()
    }

    assert(setup.hours === "12h" || setup.hours === "24h", `current-setup hours must be '12h' or '24h', not: ${setup.hours}`)
    assert(setup.set === "set-once" || setup.set === "set-always", `current-setup must be 'set-once' or 'set-always', not: ${setup.set}`)

    return setup
  }

  /***
   * get the the date settings used by Intl.DateTimeFormat()
   *
   * setupObj (obj) see parseSetup() for a definition
   * return (obj) date settings used by Intl.DateTimeFormat
   */
  const getDateSettings = (setupObj) => {
    return {
      timeZone: setupObj.timezone,
      month:    "2-digit",
      day:      "2-digit",
      year:     "numeric" // 4-digit year
    }
  }

  /***
   * get the the time settings used by Intl.DateTimeFormat()
   *
   * setupObj (obj) see parseSetup() for a definition
   * return (obj) date settings used by Intl.DateTimeFormat
   */
   const getTimeSettings = (setupObj) => {
    return {
      timeZone: setupObj.timezone,
      hour:     "2-digit",
      minute:   "2-digit",
      second:   "2-digit",
      hour12:   setupObj.hours === '12h'
    }
  }

  /* ---------------------------------------
     Helper: Get a SurveyGizmo element on the page.
             Ex: In survey 1234567 on page ID 12 the call getSgId(123, "element")
                 returns HTML element for "sgE-1234567-12-123-element"
     --------------------------------------- */
  function getSgElemById(qid, oid = "element") {
    let surveyInfo = SGAPI.surveyData[Object.keys(SGAPI.surveyData)[0]]
    let id = "sgE-" + surveyInfo.id + "-" + surveyInfo.currentpage + "-" + qid + "-" + oid
    let elem = document.getElementById(id)
    if (!elem)
      alert("Javascript error: can't find element with id = " + id)
    return elem
  }

  /***
   * getDate()
   *
   * setupObj (obj) see parseSetup() for a definition
   * dt (Date)
   * (return) Date string of current time using settings above
   */
   const getDate = (setupObj, dt) => new Intl.DateTimeFormat(setupObj.locale, getDateSettings(setupObj)).format(dt)

  /***
   * getTime()
   *
   * setupObj (obj) see parseSetup() for a definition
   * dt (Date)
   * (return) Time string of current time using settings above
   */
  const getTime = (setupObj, dt) => {
    let s = new Intl.DateTimeFormat(setupObj.locale, getTimeSettings(setupObj)).format(dt)
    s = s.replace('24:', '00:') // fixup some LOCALE will use 24 for 12AM times
    return s
  }

  /***
   * getHour()
   *
   * setupObj (obj) see parseSetup() for a definition
   * dt (Date)
   * return (string) Hour, no leading zero: 1-23
   */
  const getHour = (setupObj, dt) => {
    const options = {
      timeZone: setupObj.timezone,
      hour:     "numeric",
      hour12:   false
    }
    let s = new Intl.DateTimeFormat(setupObj.locale, options).format(dt)
    if (s === '24')
      s = '23'
    return s
  }

  /***
   * getDow()
   *
   * setupObj (obj) see parseSetup() for a definition
   * dt (Date)
   * return (string) short version of weekday name specific to setupObj.locale
   */
  const getDow = (setupObj, dt) => {
   const options = {
     timeZone: setupObj.timezone,
     weekday: 'short'
   }
   return new Intl.DateTimeFormat(setupObj.locale, options).format(dt)
  }

  /***
   * Get a timestamp that Alchemer reports can filter on.  Due to a bug you can
   * only filter using >= or <= on a pure number so 1pm Dec 1 2022 needs to be
   * recorded as: 20221201130000
   *
   * setupObj (obj) see parseSetup() for a definition
   * dt (Date)
   * return (string) yyyymmmddhhmmss: "20221201130000"
   */
  const getFilterableTimestamp = (setupObj, dt) => {
    const dateSettings = {
      timeZone: setupObj.timezone,
      month:    "2-digit",
      day:      "2-digit",
      year:     "numeric" // 4-digit year
    }
    const timeSettings = {
      timeZone: setupObj.timezone,
      hour:     "2-digit",
      minute:   "2-digit",
      second:   "2-digit",
      hour12:   false, // false for 24-hr style
    }
    const dtString = new Intl.DateTimeFormat("en-CA", dateSettings).format(dt)
    const tmString = new Intl.DateTimeFormat("en-CA", timeSettings).format(dt)

    return (dtString + tmString).replace(/\D/g,'') // strip non-numeric
  }

  /***
   * Get the Question ID based on the question / hidden value title
   *
   * title {string} question title
   * return {int/null} the question ID or null if title not found
   */
  const getQidByTitle = (title) => {
    const questionsObj = SGAPI.survey.surveyObject.questions
    const qid = Object.keys(questionsObj).find(key =>
      questionsObj[key].title.toLowerCase() === title.toLowerCase())
    return qid || null
  }

  /***
   * Set the value of qid if it exists and isn't already filled in
   *   when SET_ONLY_ONCE flag is set
   *
   * setupObj (obj) see parseSetup() for the object definition
   * title (string) title for the hidden value action that may be on the page
   * value (string) value to set
   */
  const set = (title, setupObj, value) => {
    const qid = getQidByTitle(title)
    if (qid) {
      // Only set once?  Then don't set again.
      if (setupObj.set === 'set-once' && (getSgElemById(qid).value && getSgElemById(qid).value !== 'not-set'))
        return
      getSgElemById(qid).value = value
    }
  }

  /***
   * main()
   */
  const browserDate = new Date()
  const setupObj = parseSetup()

  set(DATE_TITLE,                setupObj, getDate(setupObj, browserDate))
  set(TIME_TITLE,                setupObj, getTime(setupObj, browserDate))
  set(TIMESTAMP_TITLE,           setupObj, getDate(setupObj, browserDate) + ' ' + getTime(setupObj, browserDate))
  set(FILTERABLE_TIMESTAMP_TITLE,setupObj, getFilterableTimestamp(setupObj, browserDate))
  set(ISO_TIMESTAMP_TITLE,       setupObj, browserDate.toISOString())
  set(MILLISECONDS_TITLE,        setupObj, browserDate.getTime()) // getTime() is based on UTC and timezone independent
  set(DOW_TITLE,                 setupObj, getDow(setupObj, browserDate))
  set(HOUR_TITLE,                setupObj, getHour(setupObj, browserDate))
})


Basic Standard Market Research HR Professional Full Access Reporting
Free Individual Team & Enterprise
Feature Included In