Index: trunk/BNC/src/RTCM3/RTCM3coDecoder.cpp
===================================================================
--- trunk/BNC/src/RTCM3/RTCM3coDecoder.cpp	(revision 11058)
+++ trunk/BNC/src/RTCM3/RTCM3coDecoder.cpp	(revision 11068)
@@ -1139,28 +1139,42 @@
   }
 
+  char epoSys = 0; // system the epoch of this message is taken from
   if      (epoSecGPS != -1) {
+    epoSys = 'G';
     _lastTime.set(currentWeek, epoSecGPS);
   }
   else if (epoSecGlo != -1) {
+    epoSys = 'R';
     QDate date = dateAndTimeFromGPSweek(currentTime.gpsw(), currentTime.gpssec()).date();
     double leap = gnumleap(date.year(), date.month(), date.day());
     // IGS-SSR: the SSR Epoch Time (IDF003, 20 bit) is given in one continuous
     // time scale for all GNSS, i.e. GPS seconds of week also for GLONASS
-    // (IGS SSR v1.00) - no conversion. Old encoders (e.g. BNC before
-    // 2026-09-30) wrote UTC seconds of day instead: recognised by comparison
-    // with the GPS/Galileo epoch of the same stream and converted, with a
-    // warning, during a transition period.
+    // (IGS SSR v1.00) - no conversion. Old encoders wrote UTC instead, as
+    // seconds of day (e.g. BNC before 2026-09-30) or as seconds of week:
+    // recognised by comparison with the GPS/Galileo epoch of the same stream
+    // and converted, with a warning, during a transition period. The value
+    // in GPS time is taken modulo one day around the stream's GPS epoch,
+    // which covers both variants.
     if (_type == IGSssr && _igsRefEpoSec >= 0.0 &&
         igsLegacyEpoch(epoSecGlo, fmod(_igsRefEpoSec - leap + 7 * 86400.0, 86400.0), 86400.0)) {
-      double sow = floor(_igsRefEpoSec / 86400.0) * 86400.0 + epoSecGlo + leap;
-      if      (sow - _igsRefEpoSec >  43200.0) sow -= 86400.0;
-      else if (_igsRefEpoSec - sow >  43200.0) sow += 86400.0;
-      epoSecGlo = sow;
+      // seconds of day: outdated convention; seconds of week: erroneous
+      bool secOfWeek = (epoSecGlo >= 86400.0);
+      double dd = fmod(epoSecGlo + leap - _igsRefEpoSec, 86400.0);
+      if      (dd >  43200.0) dd -= 86400.0;
+      else if (dd < -43200.0) dd += 86400.0;
+      epoSecGlo = _igsRefEpoSec + dd;
       bncTime& warned = _legacyEpochWarned['R'];
       if (!warned.valid() || currentTime - warned >= 3600.0) {
         warned = currentTime;
-        emit newMessage(_staID.toLatin1() + ": IGS-SSR GLONASS epoch time in outdated convention "
-                        "(UTC seconds of day instead of GPS seconds of week, IGS SSR v1.00 IDF003) "
-                        "detected and converted - the SSR encoder should be updated", true);
+        if (secOfWeek) {
+          emit newMessage(_staID.toLatin1() + ": IGS-SSR GLONASS epoch time erroneously encoded "
+                          "(UTC seconds of week instead of GPS seconds of week, IGS SSR v1.00 IDF003) "
+                          "detected and converted - the SSR encoder should be corrected", true);
+        }
+        else {
+          emit newMessage(_staID.toLatin1() + ": IGS-SSR GLONASS epoch time in outdated convention "
+                          "(UTC seconds of day instead of GPS seconds of week, IGS SSR v1.00 IDF003) "
+                          "detected and converted - the SSR encoder should be updated", true);
+        }
       }
     }
@@ -1172,13 +1186,17 @@
   }
   else if (epoSecGal != -1) {
+    epoSys = 'E';
     _lastTime.set(currentWeek, epoSecGal);
   }
   else if (epoSecQzss != -1) {
+    epoSys = 'J';
     _lastTime.set(currentWeek, epoSecQzss);
   }
   else if (epoSecSbas != -1) {
+    epoSys = 'S';
     _lastTime.set(currentWeek, epoSecSbas);
   }
   else if (epoSecBds != -1) {
+    epoSys = 'C';
     // RTCM-SSR: BDT; IGS-SSR: GPS time scale for all GNSS (IDF003). Old
     // IGS-SSR encoders wrote BDT: recognised and converted as for GLONASS.
@@ -1218,4 +1236,39 @@
     while (_lastTime > currentTime + maxDiff) {
       _lastTime = _lastTime - maxDiff;
+    }
+  }
+
+  // Plausibility: the orbit/clock epochs of all systems of a stream,
+  // converted to GPS time, refer to the same update cycles as its GPS epochs. A difference
+  // of more than MAX_DIFF to the latest GPS epoch (itself not older than
+  // MAX_DIFF) indicates an erroneous encoding not covered above; the epoch
+  // is used as it is and reported at most once per hour and system.
+  // Only orbit/clock messages are checked: code and phase biases may be
+  // sent with the (older) epoch of their last update (e.g. Galileo HAS).
+  const double MAX_DIFF = 120.0;
+  bool clkOrbMsg = false;
+  for (unsigned s = 0; s < CLOCKORBIT_SATNUM; s++) {
+    if (_clkOrb.NumberOfSat[s] > 0) {
+      clkOrbMsg = true;
+    }
+  }
+  if (!clkOrbMsg) {
+    return;
+  }
+  if (_lastTime.valid() && epoSys == 'G') {
+    _lastGpsEpoch = _lastTime;
+  }
+  else if (_lastTime.valid() && epoSys != 0 && _lastGpsEpoch.valid() &&
+           fabs(currentTime - _lastGpsEpoch) <= MAX_DIFF &&
+           fabs(_lastTime - _lastGpsEpoch) > MAX_DIFF) {
+    bncTime& warned = _epochDiffWarned[QChar(epoSys)];
+    if (!warned.valid() || currentTime - warned >= 3600.0) {
+      warned = currentTime;
+      emit newMessage(QString("%1: %2 SSR epoch time %3 %4 differs by %5 s from the GPS SSR epoch time "
+                              "of the stream - erroneously encoded?")
+                      .arg(_staID).arg(epoSys)
+                      .arg(QString::fromStdString(_lastTime.datestr()))
+                      .arg(QString::fromStdString(_lastTime.timestr(0)))
+                      .arg(_lastTime - _lastGpsEpoch, 0, 'f', 0).toLatin1(), true);
     }
   }
