Customizing the interval between the start and end times of calendar events to 3 hours

Thank you.

I viewed the source code on the regular calendar page. (Lines 38 to 40)

<script type="text/javascript">
        var CCM_SECURITY_TOKEN = '1774625498:ec8887a53e3fe3dacbe5461fa6826225';
    </script>

Thank you for your cooperation.

No, below that.

My apologies.

I’m very sorry.

I will upload the image again.

Does it vary depending on the theme?

I’m trying it with Replica Pro and Atomik.

The theme I plan to use is Replica Pro.

Thank you for your confirmation.

Is it okay to use the source code for a regular calendar page instead of the admin panel?

I’m not sure you’re doing this in the correct spot because your screenshots don’t line up with what I’m seeing in my system. Yes, the theme will change some things but the fundamental placement of the ‘Header Extra Content’ should be the same. If properly added, it should appear as the last item before the closing < /HEAD > tag. When I went back and looked at your original screenshots, I see ‘Topics’ in the left nav but no other Attributes like ‘Exclude from nav’ so I’m wondering if you’re adding the JS script to the correct spot on the correct page. Here’s a screen recording of the steps I take to get this working:

3-hour-time

@dsds try this script instead…

<script>
document.addEventListener('DOMContentLoaded', function () {

  // Detects whether a time string is 12h or 24h format
  function detectFormat(value) {
    if (/am|pm/i.test(value)) return '12h';
    if (/^\d{1,2}:\d{2}$/.test(value)) return '24h';
    return null;
  }

  // Parse 12h time → minutes
  function parse12h(value) {
    var match = value.match(/^(\d{1,2}):(\d{2})(am|pm)$/i);
    if (!match) return null;

    var hours = parseInt(match[1], 10);
    var minutes = parseInt(match[2], 10);
    var period = match[3].toLowerCase();

    if (period === 'am' && hours === 12) hours = 0;
    if (period === 'pm' && hours !== 12) hours += 12;

    return hours * 60 + minutes;
  }

  // Parse 24h time → minutes
  function parse24h(value) {
    var match = value.match(/^(\d{1,2}):(\d{2})$/);
    if (!match) return null;

    var hours = parseInt(match[1], 10);
    var minutes = parseInt(match[2], 10);

    return hours * 60 + minutes;
  }

  // Convert minutes → 12h time
  function to12h(totalMinutes) {
    var hours24 = Math.floor(totalMinutes / 60) % 24;
    var minutes = totalMinutes % 60;

    var period = hours24 < 12 ? 'am' : 'pm';
    var hours12 = hours24 % 12 || 12;

    return hours12 + ':' + String(minutes).padStart(2, '0') + period;
  }

  // Convert minutes → 24h time
  function to24h(totalMinutes) {
    var hours = Math.floor(totalMinutes / 60) % 24;
    var minutes = totalMinutes % 60;

    return String(hours).padStart(2, '0') + ':' + String(minutes).padStart(2, '0');
  }

  // Smart add-hours that preserves original format
  function addHoursSmart(value, hoursToAdd) {
    var format = detectFormat(value);
    if (!format) return null;

    var minutes = format === '12h' ? parse12h(value) : parse24h(value);
    if (minutes === null) return null;

    minutes += hoursToAdd * 60;

    return format === '12h' ? to12h(minutes) : to24h(minutes);
  }

  // Snap to nearest valid option in the END dropdown
  function snapToClosestOption(ts, targetValue) {
    var options = Object.keys(ts.options);
    if (!options.length) return targetValue;

    // If exact match exists, use it
    if (ts.options[targetValue]) return targetValue;

    // Convert all options + target to minutes for comparison
    function toMinutes(v) {
      return detectFormat(v) === '12h' ? parse12h(v) : parse24h(v);
    }

    var targetMin = toMinutes(targetValue);
    var closest = options[0];
    var closestDiff = Math.abs(toMinutes(closest) - targetMin);

    options.forEach(function(opt) {
      var diff = Math.abs(toMinutes(opt) - targetMin);
      if (diff < closestDiff) {
        closest = opt;
        closestDiff = diff;
      }
    });

    return closest;
  }

  function attachTimeLogic() {

    var selects = document.querySelectorAll('select');

    var startTS = null;
    var endTS = null;

    selects.forEach(function(select) {
      if (!select.tomselect) return;

      var name = (select.name || '').toLowerCase();

      if (name.includes('start')) startTS = select.tomselect;
      if (name.includes('end')) endTS = select.tomselect;
    });

    if (!startTS || !endTS) return;
    if (startTS._smartAttached) return;
    startTS._smartAttached = true;

    var initialized = false;

    function setEnd(startValue) {
      var rawEnd = addHoursSmart(startValue, 3);
      if (!rawEnd) return;

      var snapped = snapToClosestOption(endTS, rawEnd);
      endTS.setValue(snapped);
    }

    // Initial auto-fill
    setTimeout(function () {
      var startValue = startTS.getValue();
      if (startValue) {
        setEnd(startValue);
        initialized = true;
      }
    }, 100);

    // Update on change
    startTS.on('change', function (value) {
      if (!initialized) return;
      setEnd(value);
    });

    // Keep TomSelect behaviour consistent
    if (endTS.control) {
      endTS.control.addEventListener('mousedown', function () {
        var value = endTS.getValue();
        if (!value) return;

        var option = endTS.getOption(value);
        if (option) {
          endTS.setActiveOption(option);
        }
      });
    }

  }

  var observer = new MutationObserver(function () {
    attachTimeLogic();
  });

  observer.observe(document.documentElement, {
    childList: true,
    subtree: true
  });

});
</script>


This one worked for me in a 24 hour UK timezone.

That one works for me too in Canada.

Yes, this script auto detects the time format for 12 hour or 24 hour and runs the appropriate function accordingley.

@dsds Here are some Step-by-Step instructions.

  1. Go to ‘Dashboard’ Admin Panel
  2. Go to ‘Sitemap’
  3. Click the ‘Options’ box and choose ‘Include System Pages in Sitemap’
  4. Click the ‘+’ icon next to ‘Dashboard’ folder
  5. Click the ‘+’ icon next to ‘Calendar & Events’ folder
  6. Click ‘View Calendar’
  7. Click ‘Attributes’
  8. Click ‘Header Extra Content’
  9. Paste my script into the ‘Header Extra Content box’
  10. ‘Save Changes’.
    Done!

Thank you for the video.

I believe the setup method is the same and correct.

The settings worked correctly after I followed the instructions!

It achieved the functionality I wanted.

I am very grateful.

I sincerely appreciate you modifying the complex code to make it work properly.

@ConcreteOwl
@mhawke
@Myq

Thank you to everyone who helped.

Thank you so much!!

Aren’t those the very same instructions I posted with my original solution?

I apologize if I caused any misunderstanding.

The issue has truly been resolved, so I can’t really explain it.

I’m replying via translation, so I apologize if the nuances aren’t conveyed.

I appreciate the helpful information and effort you put in.

Glad we got it solved. :+1: