From 0a7c6caedffd59011077b49cd6493c7a841a66f5 Mon Sep 17 00:00:00 2001
From: CJ Bok <cjbok@ziggo.nl>
Date: Fri, 17 Apr 2020 01:18:54 +0200
Subject: [PATCH] System Time Offset Implementation (#1101)

* System Time Offset Implementation

* Addressed @Thog's comments

* Addressed JD's comments

* Addressed @Thog's and @AcK77's comments

* formatting correction
---
 .../Configuration/ConfigurationFileFormat.cs  |   7 +-
 .../Configuration/ConfigurationState.cs       |  18 ++
 Ryujinx.HLE/HOS/Horizon.cs                    |  19 +-
 .../Services/Time/Clock/Types/TimeSpanType.cs |  15 +-
 Ryujinx/Config.json                           |   3 +-
 Ryujinx/Ui/SwitchSettings.cs                  |  78 +++++++-
 Ryujinx/Ui/SwitchSettings.glade               | 174 +++++++++++++++++-
 Ryujinx/_schema.json                          |  12 ++
 8 files changed, 320 insertions(+), 6 deletions(-)

diff --git a/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs
index f47dc4b3bb..812dc2c308 100644
--- a/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs
+++ b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs
@@ -19,7 +19,7 @@ namespace Ryujinx.Configuration
         /// <summary>
         /// The current version of the file format
         /// </summary>
-        public const int CurrentVersion = 4;
+        public const int CurrentVersion = 5;
 
         public int Version { get; set; }
 
@@ -93,6 +93,11 @@ namespace Ryujinx.Configuration
         /// </summary>
         public string SystemTimeZone { get; set; }
 
+        /// <summary>
+        /// Change System Time Offset in seconds
+        /// </summary>
+        public long SystemTimeOffset { get; set; }
+
         /// <summary>
         /// Enables or disables Docked Mode
         /// </summary>
diff --git a/Ryujinx.Common/Configuration/ConfigurationState.cs b/Ryujinx.Common/Configuration/ConfigurationState.cs
index 67628aa133..d2826d3675 100644
--- a/Ryujinx.Common/Configuration/ConfigurationState.cs
+++ b/Ryujinx.Common/Configuration/ConfigurationState.cs
@@ -158,6 +158,11 @@ namespace Ryujinx.Configuration
             /// </summary>
             public ReactiveObject<string> TimeZone { get; private set; }
 
+            /// <summary>
+            /// System Time Offset in seconds
+            /// </summary>
+            public ReactiveObject<long> SystemTimeOffset { get; private set; }
+
             /// <summary>
             /// Enables or disables Docked Mode
             /// </summary>
@@ -188,6 +193,7 @@ namespace Ryujinx.Configuration
                 Language                  = new ReactiveObject<Language>();
                 Region                    = new ReactiveObject<Region>();
                 TimeZone                  = new ReactiveObject<string>();
+                SystemTimeOffset          = new ReactiveObject<long>();
                 EnableDockedMode          = new ReactiveObject<bool>();
                 EnableMulticoreScheduling = new ReactiveObject<bool>();
                 EnableFsIntegrityChecks   = new ReactiveObject<bool>();
@@ -322,6 +328,7 @@ namespace Ryujinx.Configuration
                 SystemLanguage            = System.Language,
                 SystemRegion              = System.Region,
                 SystemTimeZone            = System.TimeZone,
+                SystemTimeOffset          = System.SystemTimeOffset,
                 DockedMode                = System.EnableDockedMode,
                 EnableDiscordIntegration  = EnableDiscordIntegration,
                 EnableVsync               = Graphics.EnableVsync,
@@ -370,6 +377,7 @@ namespace Ryujinx.Configuration
             System.Language.Value                  = Language.AmericanEnglish;
             System.Region.Value                    = Region.USA;
             System.TimeZone.Value                  = "UTC";
+            System.SystemTimeOffset.Value          = 0;
             System.EnableDockedMode.Value          = false;
             EnableDiscordIntegration.Value         = true;
             Graphics.EnableVsync.Value             = true;
@@ -504,6 +512,15 @@ namespace Ryujinx.Configuration
                 configurationFileUpdated = true;
             }
 
+            if (configurationFileFormat.Version < 5)
+            {
+                Common.Logging.Logger.PrintWarning(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 5.");
+
+                configurationFileFormat.SystemTimeOffset = 0;
+
+                configurationFileUpdated = true;
+            }
+
             Graphics.MaxAnisotropy.Value           = configurationFileFormat.MaxAnisotropy;
             Graphics.ShadersDumpPath.Value         = configurationFileFormat.GraphicsShadersDumpPath;
             Logger.EnableDebug.Value               = configurationFileFormat.LoggingEnableDebug;
@@ -518,6 +535,7 @@ namespace Ryujinx.Configuration
             System.Language.Value                  = configurationFileFormat.SystemLanguage;
             System.Region.Value                    = configurationFileFormat.SystemRegion;
             System.TimeZone.Value                  = configurationFileFormat.SystemTimeZone;
+            System.SystemTimeOffset.Value          = configurationFileFormat.SystemTimeOffset;
             System.EnableDockedMode.Value          = configurationFileFormat.DockedMode;
             System.EnableDockedMode.Value          = configurationFileFormat.DockedMode;
             EnableDiscordIntegration.Value         = configurationFileFormat.EnableDiscordIntegration;
diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs
index f6021ec780..ccb67edfea 100644
--- a/Ryujinx.HLE/HOS/Horizon.cs
+++ b/Ryujinx.HLE/HOS/Horizon.cs
@@ -9,6 +9,7 @@ using LibHac.Ns;
 using LibHac.Spl;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
+using Ryujinx.Configuration;
 using Ryujinx.HLE.FileSystem.Content;
 using Ryujinx.HLE.HOS.Font;
 using Ryujinx.HLE.HOS.Kernel.Common;
@@ -224,8 +225,24 @@ namespace Ryujinx.HLE.HOS
             // We assume the rtc is system time.
             TimeSpanType systemTime = TimeSpanType.FromSeconds((long)rtcValue);
 
+            // Configure and setup internal offset
+            TimeSpanType internalOffset = TimeSpanType.FromSeconds(ConfigurationState.Instance.System.SystemTimeOffset);
+            
+            TimeSpanType systemTimeOffset = new TimeSpanType(systemTime.NanoSeconds + internalOffset.NanoSeconds);
+
+            if (systemTime.IsDaylightSavingTime() && !systemTimeOffset.IsDaylightSavingTime())
+            {
+                internalOffset = internalOffset.AddSeconds(3600L);
+            }
+            else if (!systemTime.IsDaylightSavingTime() && systemTimeOffset.IsDaylightSavingTime())
+            {
+                internalOffset = internalOffset.AddSeconds(-3600L);
+            }
+
+            internalOffset = new TimeSpanType(-internalOffset.NanoSeconds);
+
             // First init the standard steady clock
-            TimeServiceManager.Instance.SetupStandardSteadyClock(null, clockSourceId, systemTime, TimeSpanType.Zero, TimeSpanType.Zero, false);
+            TimeServiceManager.Instance.SetupStandardSteadyClock(null, clockSourceId, systemTime, internalOffset, TimeSpanType.Zero, false);
             TimeServiceManager.Instance.SetupStandardLocalSystemClock(null, new SystemClockContext(), systemTime.ToSeconds());
 
             if (NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes))
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs
index c336ad4196..89497d824f 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs
@@ -1,4 +1,5 @@
-using System.Runtime.InteropServices;
+using System;
+using System.Runtime.InteropServices;
 
 namespace Ryujinx.HLE.HOS.Services.Time.Clock
 {
@@ -9,6 +10,8 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
 
         public static readonly TimeSpanType Zero = new TimeSpanType(0);
 
+        private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
         public long NanoSeconds;
 
         public TimeSpanType(long nanoSeconds)
@@ -21,6 +24,16 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
             return NanoSeconds / NanoSecondsPerSecond;
         }
 
+        public TimeSpanType AddSeconds(long seconds)
+        {
+            return new TimeSpanType(NanoSeconds + (seconds * NanoSecondsPerSecond));
+        }
+
+        public bool IsDaylightSavingTime()
+        {
+            return UnixEpoch.AddSeconds(ToSeconds()).ToLocalTime().IsDaylightSavingTime();
+        }
+
         public static TimeSpanType FromSeconds(long seconds)
         {
             return new TimeSpanType(seconds * NanoSecondsPerSecond);
diff --git a/Ryujinx/Config.json b/Ryujinx/Config.json
index 8a36555012..7a5fe97239 100644
--- a/Ryujinx/Config.json
+++ b/Ryujinx/Config.json
@@ -1,5 +1,5 @@
 {
-    "version": 4,
+    "version": 5,
     "max_anisotropy": -1,
     "graphics_shaders_dump_path": "",
     "logging_enable_debug": false,
@@ -14,6 +14,7 @@
     "system_language": "AmericanEnglish",
     "system_region": "USA",
     "system_time_zone": "UTC",
+    "system_time_offset": 0,
     "docked_mode": false,
     "enable_discord_integration": true,
     "enable_vsync": true,
diff --git a/Ryujinx/Ui/SwitchSettings.cs b/Ryujinx/Ui/SwitchSettings.cs
index 8ff4042788..700d059687 100644
--- a/Ryujinx/Ui/SwitchSettings.cs
+++ b/Ryujinx/Ui/SwitchSettings.cs
@@ -20,6 +20,8 @@ namespace Ryujinx.Ui
 
         private static bool _listeningForKeypress;
 
+        private long _systemTimeOffset;
+
 #pragma warning disable CS0649
 #pragma warning disable IDE0044
         [GUI] Window       _settingsWin;
@@ -42,6 +44,16 @@ namespace Ryujinx.Ui
         [GUI] ComboBoxText _systemLanguageSelect;
         [GUI] ComboBoxText _systemRegionSelect;
         [GUI] ComboBoxText _systemTimeZoneSelect;
+        [GUI] SpinButton   _systemTimeYearSpin;
+        [GUI] SpinButton   _systemTimeMonthSpin;
+        [GUI] SpinButton   _systemTimeDaySpin;
+        [GUI] SpinButton   _systemTimeHourSpin;
+        [GUI] SpinButton   _systemTimeMinuteSpin;
+        [GUI] Adjustment   _systemTimeYearSpinAdjustment;
+        [GUI] Adjustment   _systemTimeMonthSpinAdjustment;
+        [GUI] Adjustment   _systemTimeDaySpinAdjustment;
+        [GUI] Adjustment   _systemTimeHourSpinAdjustment;
+        [GUI] Adjustment   _systemTimeMinuteSpinAdjustment;
         [GUI] CheckButton  _custThemeToggle;
         [GUI] Entry        _custThemePath;
         [GUI] ToggleButton _browseThemePath;
@@ -248,6 +260,7 @@ namespace Ryujinx.Ui
             _custThemePath.Buffer.Text           = ConfigurationState.Instance.Ui.CustomThemePath;
             _graphicsShadersDumpPath.Buffer.Text = ConfigurationState.Instance.Graphics.ShadersDumpPath;
             _fsLogSpinAdjustment.Value           = ConfigurationState.Instance.System.FsGlobalAccessLogMode;
+            _systemTimeOffset                    = ConfigurationState.Instance.System.SystemTimeOffset;
 
             _gameDirsBox.AppendColumn("", new CellRendererText(), "text", 0);
             _gameDirsBoxStore  = new ListStore(typeof(string));
@@ -266,9 +279,71 @@ namespace Ryujinx.Ui
             }
 
             _listeningForKeypress = false;
+
+            //Setup system time spinners
+            UpdateSystemTimeSpinners();
+        }
+
+        private void UpdateSystemTimeSpinners()
+        {
+            //Unbind system time spin events
+            _systemTimeYearSpin.ValueChanged   -= SystemTimeSpin_ValueChanged;
+            _systemTimeMonthSpin.ValueChanged  -= SystemTimeSpin_ValueChanged;
+            _systemTimeDaySpin.ValueChanged    -= SystemTimeSpin_ValueChanged;
+            _systemTimeHourSpin.ValueChanged   -= SystemTimeSpin_ValueChanged;
+            _systemTimeMinuteSpin.ValueChanged -= SystemTimeSpin_ValueChanged;
+
+            //Apply actual system time + SystemTimeOffset to system time spin buttons
+            DateTime systemTime = DateTime.Now.AddSeconds(_systemTimeOffset);
+
+            _systemTimeYearSpinAdjustment.Value   = systemTime.Year;
+            _systemTimeMonthSpinAdjustment.Value  = systemTime.Month;
+            _systemTimeDaySpinAdjustment.Value    = systemTime.Day;
+            _systemTimeHourSpinAdjustment.Value   = systemTime.Hour;
+            _systemTimeMinuteSpinAdjustment.Value = systemTime.Minute;
+
+            //Format spin buttons text to include leading zeros
+            _systemTimeYearSpin.Text   = systemTime.Year.ToString("0000");
+            _systemTimeMonthSpin.Text  = systemTime.Month.ToString("00");
+            _systemTimeDaySpin.Text    = systemTime.Day.ToString("00");
+            _systemTimeHourSpin.Text   = systemTime.Hour.ToString("00");
+            _systemTimeMinuteSpin.Text = systemTime.Minute.ToString("00");
+
+            //Bind system time spin button events
+            _systemTimeYearSpin.ValueChanged   += SystemTimeSpin_ValueChanged;
+            _systemTimeMonthSpin.ValueChanged  += SystemTimeSpin_ValueChanged;
+            _systemTimeDaySpin.ValueChanged    += SystemTimeSpin_ValueChanged;
+            _systemTimeHourSpin.ValueChanged   += SystemTimeSpin_ValueChanged;
+            _systemTimeMinuteSpin.ValueChanged += SystemTimeSpin_ValueChanged;
         }
 
         //Events
+        private void SystemTimeSpin_ValueChanged(Object sender, EventArgs e)
+        {
+            int year   = _systemTimeYearSpin.ValueAsInt;
+            int month  = _systemTimeMonthSpin.ValueAsInt;
+            int day    = _systemTimeDaySpin.ValueAsInt;
+            int hour   = _systemTimeHourSpin.ValueAsInt;
+            int minute = _systemTimeMinuteSpin.ValueAsInt;
+
+            if (!DateTime.TryParse(year + "-" + month + "-" + day + " " + hour + ":" + minute, out DateTime newTime))
+            {
+                UpdateSystemTimeSpinners();
+
+                return;
+            }
+
+            newTime = newTime.AddSeconds(DateTime.Now.Second).AddMilliseconds(DateTime.Now.Millisecond);
+
+            long systemTimeOffset = (long)Math.Ceiling((newTime - DateTime.Now).TotalMinutes) * 60L;
+
+            if (_systemTimeOffset != systemTimeOffset)
+            {
+                _systemTimeOffset = systemTimeOffset;
+                UpdateSystemTimeSpinners();
+            }
+        }
+
         private void Button_Pressed(object sender, EventArgs args, ToggleButton button)
         {
             if (_listeningForKeypress == false)
@@ -467,7 +542,8 @@ namespace Ryujinx.Ui
             ConfigurationState.Instance.Ui.GameDirs.Value                  = gameDirs;
             ConfigurationState.Instance.System.FsGlobalAccessLogMode.Value = (int)_fsLogSpinAdjustment.Value;
 
-            ConfigurationState.Instance.System.TimeZone.Value = _systemTimeZoneSelect.ActiveId;
+            ConfigurationState.Instance.System.TimeZone.Value              = _systemTimeZoneSelect.ActiveId;
+            ConfigurationState.Instance.System.SystemTimeOffset.Value      = _systemTimeOffset;
 
             MainWindow.SaveConfig();
             MainWindow.ApplyTheme();
diff --git a/Ryujinx/Ui/SwitchSettings.glade b/Ryujinx/Ui/SwitchSettings.glade
index f9c8b1c9ee..7415e76e44 100644
--- a/Ryujinx/Ui/SwitchSettings.glade
+++ b/Ryujinx/Ui/SwitchSettings.glade
@@ -7,6 +7,36 @@
     <property name="step_increment">1</property>
     <property name="page_increment">10</property>
   </object>
+  <object class="GtkAdjustment" id="_systemTimeYearSpinAdjustment">
+    <property name="lower">2000</property>
+    <property name="upper">2060</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
+  <object class="GtkAdjustment" id="_systemTimeMonthSpinAdjustment">
+    <property name="lower">1</property>
+    <property name="upper">12</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">5</property>
+  </object>
+  <object class="GtkAdjustment" id="_systemTimeDaySpinAdjustment">
+    <property name="lower">1</property>
+    <property name="upper">31</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">5</property>
+  </object>
+  <object class="GtkAdjustment" id="_systemTimeHourSpinAdjustment">
+    <property name="lower">0</property>
+    <property name="upper">23</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">5</property>
+  </object>
+  <object class="GtkAdjustment" id="_systemTimeMinuteSpinAdjustment">
+    <property name="lower">0</property>
+    <property name="upper">59</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">5</property>
+  </object>
   <object class="GtkDialog" id="_settingsWin">
     <property name="can_focus">False</property>
     <property name="title" translatable="yes">Ryujinx - Settings</property>
@@ -260,6 +290,148 @@
                                     <property name="position">1</property>
                                   </packing>
                                 </child>
+                                <child>
+                                  <object class="GtkBox">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="halign">end</property>
+                                        <property name="label" translatable="yes">System Time:</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="padding">5</property>
+                                        <property name="position">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkSpinButton" id="_systemTimeYearSpin">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="margin_left">5</property>
+                                        <property name="orientation">vertical</property>
+                                        <property name="adjustment">_systemTimeYearSpinAdjustment</property>
+                                        <property name="wrap">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="halign">end</property>
+                                        <property name="label" translatable="no">-</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="padding">5</property>
+                                        <property name="position">2</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkSpinButton" id="_systemTimeMonthSpin">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="margin_left">0</property>
+                                        <property name="orientation">vertical</property>
+                                        <property name="adjustment">_systemTimeMonthSpinAdjustment</property>
+                                        <property name="wrap">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="position">3</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="halign">end</property>
+                                        <property name="label" translatable="no">-</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="padding">5</property>
+                                        <property name="position">4</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkSpinButton" id="_systemTimeDaySpin">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="margin_left">0</property>
+                                        <property name="orientation">vertical</property>
+                                        <property name="adjustment">_systemTimeDaySpinAdjustment</property>
+                                        <property name="wrap">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="position">5</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkSpinButton" id="_systemTimeHourSpin">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="margin_left">25</property>
+                                        <property name="orientation">vertical</property>
+                                        <property name="adjustment">_systemTimeHourSpinAdjustment</property>
+                                        <property name="wrap">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="position">6</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="halign">end</property>
+                                        <property name="label" translatable="no">:</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="padding">5</property>
+                                        <property name="position">7</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkSpinButton" id="_systemTimeMinuteSpin">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="margin_left">0</property>
+                                        <property name="orientation">vertical</property>
+                                        <property name="adjustment">_systemTimeMinuteSpinAdjustment</property>
+                                        <property name="wrap">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="position">8</property>
+                                      </packing>
+                                    </child>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">2</property>
+                                  </packing>
+                                </child>
                                 <child>
                                   <object class="GtkCheckButton" id="_discordToggle">
                                     <property name="label" translatable="yes">Enable Discord Rich Presence</property>
@@ -274,7 +446,7 @@
                                     <property name="expand">False</property>
                                     <property name="fill">True</property>
                                     <property name="padding">5</property>
-                                    <property name="position">2</property>
+                                    <property name="position">3</property>
                                   </packing>
                                 </child>
                               </object>
diff --git a/Ryujinx/_schema.json b/Ryujinx/_schema.json
index 143716b6c9..f075b608f9 100644
--- a/Ryujinx/_schema.json
+++ b/Ryujinx/_schema.json
@@ -422,6 +422,18 @@
         "USA"
       ]
     },
+    "system_time_offset": {
+      "$id": "#/properties/system_time_offset",
+      "type": "integer",
+      "title": "System Time Offset",
+      "description": "System time offset in seconds.",
+      "default": 0,
+      "examples": [
+        -3600,
+        0,
+        3600
+      ]
+    },
     "docked_mode": {
       "$id": "#/properties/docked_mode",
       "type": "boolean",