Menu="OtherSettings"
Title="Date and Time"
Icon="icon-clock"
Tag="clock-o"
---
<?php
/* Copyright 2005-2025, Lime Technology
 * Copyright 2012-2025, Bergware International.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version 2,
 * as published by the Free Software Foundation.
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 */
?>
<?php
$validIP4 = "^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$";
$validIP6 = "^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(:|([0-9a-fA-F]{1,4}:)+):(([0-9a-fA-F]{1,4}:)*[0-9a-fA-F]{1,4})?)$";
$validMAC = "^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$";
$tmzones  = file("$docroot/webGui/include/timezones.key", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$system   = "/sys/class/net";
$hwclock  = $ports = $member = [];

exec("ls --indicator-style=none $system|grep -P '^(bond|br|eth)[0-9]*$'", $net);
foreach ($net as $port) {
    switch (preg_replace('/[0-9]+$/', '', $port)) {
        case 'bond':
            if (!file_exists($system.'/'.str_replace('bond', 'br', $port))) {
                $ports[] = $port;
            }
            $member = array_merge($member, explode(' ', exec("cat $system/$port/bonding/slaves")));
            break;
        case 'br':
            $member = array_merge($member, explode(' ', exec("ls --indicator-style=none $system/$port/brif")));
            break;
        case 'eth':
            if (!in_array($port, $member)) {
                $ports[] = $port;
            }
            break;
    }
}
foreach ($ports as $port) {
    $hwclock[$port] = exec("ethtool -T $port|grep -Pom1 '^PTP Hardware Clock: \K.+'") == 'none' ? 'disabled' : '';
}
?>
<form markdown="1" name="datetime_settings" method="POST" action="/update.htm" target="progressFrame" onchange="signal(this)">
<input type="hidden" name="setDateTime" value="apply">

_(Current date and time)_:
: <?=_(my_time(time()), 0)?>

_(Date format)_:
: <select name="display_date" onchange="presetTime(this.form)">
  <?=mk_option($display['date'], "%c", _('System Setting'))?>
  <?=mk_option($display['date'], "%A, %Y %B %e", _('Day, YYYY Month D'))?>
  <?=mk_option($display['date'], "%A, %e %B %Y", _('Day, D Month YYYY'))?>
  <?=mk_option($display['date'], "%A, %B %e, %Y", _('Day, Month D, YYYY'))?>
  <?=mk_option($display['date'], "%A, %m/%d/%Y", _('Day, MM/DD/YYYY'))?>
  <?=mk_option($display['date'], "%A, %d-%m-%Y", _('Day, DD-MM-YYYY'))?>
  <?=mk_option($display['date'], "%A, %d.%m.%Y", _('Day, DD.MM.YYYY'))?>
  <?=mk_option($display['date'], "%A, %Y-%m-%d", _('Day, YYYY-MM-DD'))?>
  </select>

_(Time format)_:
: <select name="display_time">
  <?=mk_option($display['time'], "%I:%M %p", _('12 hours'))?>
  <?=mk_option($display['time'], "%R", _('24 hours'))?>
  </select>

_(Time zone)_:
: <select name="timeZone"><?php
  foreach ($tmzones as $zone) {
      [$timezone, $city] = my_explode('|', $zone);
      echo mk_option($var['timeZone'], $timezone, $city);
  }
?></select>

:timezone_help:

_(Time state)_:
: <span id="timeState"><i class="fa fa-spin fa-spinner"></i></span>

_(Time sync)_:
: <select name="USE_NTP" onchange="updatePage(this.form,'slow')">
  <?=mk_option($var['USE_NTP'], "yes", _('NTP'))?>
  <?=mk_option($var['USE_NTP'], "yes", _('PTP'))?>
  <?=mk_option($var['USE_NTP'], "no", _('Manual'))?>
  </select>
  <span id="noports" class="red-text"><i class="fa fa-warning"></i> _(No suitable interfaces found for PTP)_</span>

<div markdown="1" id="ntp-setup" class="extra">
_(NTP interval)_:
: <select name="NTP_POLL">
  <?=mk_option(_var($NTP, 'POLL'), "", _('Default'))?>
  <?=mk_option(_var($NTP, 'POLL'), "8", _('Slow'))?>
  <?=mk_option(_var($NTP, 'POLL'), "5", _('Medium'))?>
  <?=mk_option(_var($NTP, 'POLL'), "3", _('Fast'))?>
  </select>
  <span class="input-instructions orange-text">_(Use DEFAULT setting when public NTP servers are defined)_</span>

_(NTP server)_ 1:
: <input type="text" name="NTP_SERVER1" maxlength="40" value="<?=htmlspecialchars($var['NTP_SERVER1'])?>">
  <span class="input-instructions orange-text">_(Input a NTP server name, NTP pool name or IP address)_</span>

_(NTP server)_ 2:
: <input type="text" name="NTP_SERVER2" maxlength="40" value="<?=htmlspecialchars($var['NTP_SERVER2'])?>">

_(NTP server)_ 3:
: <input type="text" name="NTP_SERVER3" maxlength="40" value="<?=htmlspecialchars($var['NTP_SERVER3'])?>">

_(NTP server)_ 4:
: <input type="text" name="NTP_SERVER4" maxlength="40" value="<?=htmlspecialchars($var['NTP_SERVER4'])?>">

</div>
<div markdown="1" id="ptp-setup" class="extra">
_(PTP profile)_:
: PTPv2 (IEEE 1588)

_(PTP transport)_:
: <select name="PTP_TRANSPORT" onchange="updatePage(this.form,'slow')">
  <?=mk_option(_var($PTP, 'TRANSPORT'), "UDPv4", _('UDPv4'))?>
  <?=mk_option(_var($PTP, 'TRANSPORT'), "UDPv6", _('UDPv6'))?>
  <?=mk_option(_var($PTP, 'TRANSPORT'), "L2", _('IEEE 802.3'))?>
  </select>

_(PTP mode)_:
: <select name="PTP_MODE" onchange="updatePage(this.form,'slow')">
  <?=mk_option(_var($PTP, 'MODE'), "multicast", _('Multicast'))?>
  <?=mk_option(_var($PTP, 'MODE'), "unicast", _('Unicast'))?>
  </select>

<div markdown="1" id="unicast" class="extra">
_(PTP server)_ 1:
: <input type="text" name="PTP_SERVER1" maxlength="40" value="<?=htmlspecialchars(_var($PTP, 'SERVER1'))?>">
  <span class="ipv4 orange-text">_(Input a IPv4 address)_</span><span class="ipv6 orange-text">_(Input a IPv6 address)_</span><span class="mac orange-text">_(Input a MAC address)_</span>

_(PTP server)_ 2:
: <input type="text" name="PTP_SERVER2" maxlength="40" value="<?=htmlspecialchars(_var($PTP, 'SERVER2'))?>">

_(PTP server)_ 3:
: <input type="text" name="PTP_SERVER3" maxlength="40" value="<?=htmlspecialchars(_var($PTP, 'SERVER3'))?>">

_(PTP server)_ 4:
: <input type="text" name="PTP_SERVER4" maxlength="40" value="<?=htmlspecialchars(_var($PTP, 'SERVER4'))?>">

</div>
_(PTP interface)_:
: <select name="PTP_PORT" onchange="hwclock(this.form,this.value)">
  <?if (empty($ports)) echo mk_option(0, '', '---');?>
  <?foreach($ports as $port):?>
  <?=mk_option(_var($PTP, 'PORT', 'eth0'), $port, $port)?>
  <?endforeach;?>
  </select>

_(PTP clock)_:
: <select name="PTP_CLOCK">
  <?=mk_option(_var($PTP, 'CLOCK'), "hardware", _('Hardware'), empty($ports) ? 'disabled' : _var($hwclock, _var($PTP, 'PORT')))?>
  <?=mk_option(_var($PTP, 'CLOCK'), "software", _('Software'))?>
  </select>

</div>
<div markdown="1" id="manual-setup" class="extra">
_(New date and time)_:
: <input type="text" name="newDateTime" maxlength="20" value="<?=my_time(time(), "%F %T")?>">
  <span class="orange-text">_(Input the correct date and time manually)_</span>

:current_time_help:

</div>
&nbsp;
: <span class="inline-block">
    <input type="button" value="_(Apply)_" onclick="dispatch(this.form)" disabled>
    <input type="button" value="_(Done)_" onclick="done()">
  </span>
</form>

<script>
// update time service
var service = false;
var oldDate, oldTime, oldZone;

// ptp daemon is running?
var ptpd = <?=exec("pgrep --ns $$ -cf /usr/sbin/ptp4l") ? 'true' : 'false'?>;

function hwclock(form,port) {
  // disable hardware clock if not supported
  var hwc = {};
<?foreach ($hwclock as $port => $disabled):?>
  hwc.<?=$port?> = <?=$disabled ? 'true' : 'false'?>;
<?endforeach;?>
  form.PTP_CLOCK.options[0].disabled = hwc[port];
}

function signal(form) {
  if (service) return;
  if (form.display_date.selectedIndex != oldDate) {
    oldDate = form.display_date.selectedIndex;
    return;
  }
  if (form.display_time.selectedIndex != oldTime) {
    oldTime = form.display_time.selectedIndex;
    return
  }
  // time service needs update
  service = true;
}

function disableButtons(state) {
  setTimeout(function(){$('input[type=button]').prop('disabled',state);});
}

function dispatch(form) {
  var save = {};
  var valid = true;
  disableButtons(true);
  if (form.USE_NTP.selectedIndex==1 && form.PTP_MODE.selectedIndex==1) {
    let tr = form.PTP_TRANSPORT.value;
    let pattern = tr=='L2' ? /<?=$validMAC?>/ : (tr=='UDPv4' ? /<?=$validIP4?>/ : /<?=$validIP6?>/);
    // validate syntax of ptp server addresses
    $(form).find('input[name^="PTP_SERVER"]').each(function(){
      var text = $(this).val();
      if (text!='' && pattern.test(text)==false) valid = false;
    });
  } else if (form.USE_NTP.selectedIndex==1) {
    // clear ptp server addresses
    $('input[name^="PTP_SERVER"]').val('');
  }
  if (valid) {
    clearTimeout(timers.timeState);
    $('#timeState').html('<i class="fa fa-spin fa-spinner"></i>');
    let index = form.USE_NTP.selectedIndex;
    let cmd = index==1 ? (ptpd ? 'restart' : 'start') : 'stop';
    save['#cfg'] = "/boot/config/plugins/dynamix/dynamix.cfg";
    save.PTP_SYNC = index==1 ? 'yes' : 'no';
    $(form).find('select[name^="display_"],select[name="NTP_POLL"],select[name^="PTP_"],input[name^="display_"],input[name^="PTP_"]').each(function(){
      save[$(this).attr('name')] = $(this).val();
      // exclude setting for emhttpd
      $(this).prop('disabled',true);
    });
    // save dispatched variables
    $.post('/webGui/include/Dispatcher.php',save,function(){
      if (service) {
        // start/stop ptp daemon separately (emhttpd does not support it)
        $.post('/webGui/include/StartStopPTP.php',{cmd:cmd},function(){
          // reset timezone when it has changed
          if (form.timeZone.selectedIndex != oldZone) $.post('/webGui/include/ResetTZ.php');
          // start/stop ntp daemon (using emhttpd)
          form.submit();
        });
      } else {
        // no time service update needed
        refresh();
      }
    });
  } else {
    disableButtons(false);
    swal("_(Wrong PTP server address)_");
  }
}

function presetTime(form) {
  var system = form.display_date.selectedIndex==0;
  if (system) form.display_time.selectedIndex = 0;
  form.display_time.disabled = system;
}

function updatePage(form,step) {
  if (form.USE_NTP.selectedIndex==0) {
    // ntp
    $('#ptp-setup,#manual-setup').hide(step);
    $('#ntp-setup').show(step);
    $('#noports').hide();
    form.newDateTime.disabled=true;
    form.NTP_SERVER1.disabled=false;
    form.NTP_SERVER2.disabled=false;
    form.NTP_SERVER3.disabled=false;
    form.NTP_SERVER4.disabled=false;
  } else if (form.USE_NTP.selectedIndex==1) {
    // ptp
    $('#ntp-setup,#manual-setup').hide(step);
    $('#ptp-setup').show(step);
<?if (empty($ports)):?>
    $('#noports').show();
<?else:?>
    $('#noports').hide();
<?endif;?>
    if (form.PTP_MODE.selectedIndex==0) $('#unicast').hide(step); else $('#unicast').show(step);
    form.newDateTime.disabled=true;
    form.NTP_SERVER1.disabled=true;
    form.NTP_SERVER2.disabled=true;
    form.NTP_SERVER3.disabled=true;
    form.NTP_SERVER4.disabled=true;
    $('span.ipv4,span.ipv6,span.mac').hide();
    switch (form.PTP_TRANSPORT.selectedIndex) {
      case 0: $('span.ipv4').show(); break;
      case 1: $('span.ipv6').show(); break;
      case 2: $('span.mac').show(); break;
    }
  } else {
    // manual
    $('#ntp-setup,#ptp-setup').hide(step);
    $('#manual-setup').show(step);
    $('#noports').hide();
    form.newDateTime.disabled=false;
    form.NTP_SERVER1.disabled=true;
    form.NTP_SERVER2.disabled=true;
    form.NTP_SERVER3.disabled=true;
    form.NTP_SERVER4.disabled=true;
  }
}

function timeState() {
  $.post('/webGui/include/DashboardApps.php',{ntp:'ntp'},function(ntp){$('#timeState').html(ntp);});
  timers.timeState = setTimeout(timeState,2000);
}

$(function() {
  var form = document.datetime_settings;
  presetTime(form);
  oldDate = form.display_date.selectedIndex;
  oldTime = form.display_time.selectedIndex;
  oldZone = form.timeZone.selectedIndex;
  form.USE_NTP.selectedIndex = <?=_var($PTP,'SYNC') == 'yes' ? 1 : ($var['USE_NTP'] == 'yes' ? 0 : 2)?>;
<?if (empty($ports)):?>
  if (form.USE_NTP.selectedIndex==1) $('#noports').show();
<?endif;?>
  updatePage(form);
  timeState();
});
</script>
