How to make a Rust time command plugin (/time chat command)
A `/time` chat command that reports the in-game clock, the real UTC server time, and the minutes until the next sunrise or sunset. It reads the server's `env.time` and `env.daylength` values and needs no gameplay hooks.
Rust's own in-game clock is readable once you know it, but players who just joined often ask for a quick text answer instead of opening the map or squinting at the sun. A /time command solves that: it prints the current in-game hour, the real server time, and how long until daylight changes.
The tricky part is that day length on a Rust server is not fixed. The server exposes env.time as the current hour from 0 to 24, but a full cycle usually runs shorter than 24 real hours, so you have to convert the remaining fraction of the day into minutes using the actual day length. The plugin below reads env.time and env.daylength with ConsoleSystem.GetValue, converts them to a real-world countdown, and adds configurable sunrise and sunset boundaries. The reply is a single localized chat line.
The plugin code
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Time Command", "RustPluginGenerator", "1.0.0")]
[Description("Adds a /time chat command that reports the in-game time, real server time and the countdown to sunrise or sunset.")]
public class TimeCommand : RustPlugin
{
private const string PermUse = "timecommand.use";
private PluginConfig config;
private class PluginConfig
{
[JsonProperty("Sunrise hour (0-23)")]
public int SunriseHour = 6;
[JsonProperty("Sunset hour (0-23)")]
public int SunsetHour = 20;
}
protected override void LoadDefaultConfig() => config = new PluginConfig();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<PluginConfig>();
if (config == null) throw new Exception();
}
catch
{
PrintWarning("Configuration file is invalid, using defaults");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["Reply"] = "In-game time: {0} | UTC: {1} | Next {2} in {3} ({4})",
["Sunrise"] = "sunrise",
["Sunset"] = "sunset",
["NoPermission"] = "You do not have permission to use this command.",
["Unavailable"] = "The server clock is unavailable right now."
}, this);
}
private string Msg(string key, string userId = null, params object[] args) =>
string.Format(lang.GetMessage(key, this, userId), args);
private void Init()
{
permission.RegisterPermission(PermUse, this);
}
[ChatCommand("time")]
private void CmdTime(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermUse))
{
SendReply(player, Msg("NoPermission", player.UserIDString));
return;
}
float timeOfDay;
float dayLength;
if (!TryReadClock(out timeOfDay, out dayLength))
{
SendReply(player, Msg("Unavailable", player.UserIDString));
return;
}
int sunrise = Mathf.Clamp(config.SunriseHour, 0, 23);
int sunset = Mathf.Clamp(config.SunsetHour, 0, 23);
bool countingToSunrise = timeOfDay >= sunset || timeOfDay < sunrise;
int targetHour = countingToSunrise ? sunrise : sunset;
string label = Msg(countingToSunrise ? "Sunrise" : "Sunset", player.UserIDString);
float hoursAhead = targetHour - timeOfDay;
if (hoursAhead < 0f) hoursAhead += 24f;
if (hoursAhead > 24f) hoursAhead -= 24f;
int minutesAhead = Mathf.RoundToInt(hoursAhead / 24f * dayLength);
string inGame = FormatHour(timeOfDay);
string realTime = DateTime.UtcNow.ToString("HH:mm") + " UTC";
string target = FormatHour(targetHour);
SendReply(player, Msg("Reply", player.UserIDString, inGame, realTime, label, FormatMinutes(minutesAhead), target));
}
private bool TryReadClock(out float timeOfDay, out float dayLength)
{
timeOfDay = 0f;
dayLength = 45f;
string timeValue = ConsoleSystem.Run(ConsoleSystem.Option.Server.Quiet(), "env.time");
if (string.IsNullOrEmpty(timeValue) || !float.TryParse(timeValue, out timeOfDay))
return false;
string lengthValue = ConsoleSystem.Run(ConsoleSystem.Option.Server.Quiet(), "env.daylength");
float parsedLength;
if (!string.IsNullOrEmpty(lengthValue) && float.TryParse(lengthValue, out parsedLength) && parsedLength > 0f)
dayLength = parsedLength;
timeOfDay %= 24f;
if (timeOfDay < 0f) timeOfDay += 24f;
return true;
}
private static string FormatHour(float hour)
{
int total = Mathf.RoundToInt(hour * 60f) % (24 * 60);
if (total < 0) total += 24 * 60;
return string.Format("{0:00}:{1:00}", total / 60, total % 60);
}
private static string FormatMinutes(int minutes)
{
if (minutes <= 0) return "under a minute";
int h = minutes / 60;
int m = minutes % 60;
if (h == 0) return m + "m";
return h + "h " + m + "m";
}
}
}Config file
Created automatically on first load at oxide/config/TimeCommand.json.
{
"Sunrise hour (0-23)": 6,
"Sunset hour (0-23)": 20
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/time | Shows the in-game time, the real UTC server time, and the countdown to the next sunrise or sunset. | timecommand.use |
| Permission | Grants |
|---|---|
timecommand.use | Allows a player to run the /time chat command. |
How the code works
Read the game clock from server variables
TryReadClockcallsConsoleSystem.GetValue("env.time")to get the current hour andConsoleSystem.GetValue("env.daylength")to get how many real minutes a full in-game day lasts. Both are strings, so the method parses them withfloat.TryParse. Ifenv.timeis missing or unparsable it returns false and the command replies with theUnavailablemessage.env.daylengthfalls back to 45 minutes if it is missing or zero.Normalize the hour and pick the next event
TryReadClockwraps the parsed hour into 0..24 with%.CmdTimeclampsSunriseHourandSunsetHourfrom the config to 0..23. If the current time is at or past sunset or before sunrise it counts toward sunrise, otherwise toward sunset, and picks the matchingSunriseorSunsetlang key as the label.Convert the fraction of a day into minutes
The plugin computes
hoursAheadastargetHour - timeOfDay, adds 24 if it is negative, and subtracts 24 if it is above 24. It then scales that fraction by the day length:Mathf.RoundToInt(hoursAhead / 24f * dayLength).FormatMinutesturns the result intoXmorXh Ym, and reportsunder a minutewhen nothing is left.Format the in-game clock
FormatHouraccepts a float hour and converts it to whole minutes withMathf.RoundToInt(hour * 60f), wraps it into a single day with% (24 * 60), and formats it asHH:MMusingstring.Format. The current in-game time and the target sunrise or sunset hour both pass through it, so odd config values still print cleanly.Real server time
The real-world time comes from
DateTime.UtcNow.ToString("HH:mm")withUTCappended.DateTime.Nowwould show each player's local zone, which is not useful on a shared server. Every other number is centralized in the config, so if your community wants a different clock this is the single line to change.Permission and chat reply
The
[ChatCommand("time")]methodCmdTimereceives(BasePlayer player, string command, string[] args). It checksplayer != nullandpermission.UserHasPermission(player.UserIDString, PermUse), wherePermUseistimecommand.use, registered inInit. The single reply goes throughMsg("Reply", ...), so thelangfile can translate or reword it without touching code.Config load and reload
LoadDefaultConfigbuildsPluginConfigwithSunriseHour6 andSunsetHour20.LoadConfigreads the file, warns and falls back to defaults if parsing fails, then callsSaveConfig. Editoxide/config/TimeCommand.jsonand runoxide.reload TimeCommandto apply new hours.
Install it on your server
Save TimeCommand.cs
Keep the file name exactly as the class name. Oxide matches them.Upload it to the plugins folder
Oxide:oxide/plugins/. Carbon:carbon/plugins/. The server compiles and loads it within a few seconds, no restart needed.Edit the config if you want
On first load the default config is written tooxide/config/TimeCommand.json(Carbon:carbon/configs/TimeCommand.json). After editing, reload it:oxide.reload TimeCommand c.reload TimeCommand # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip:oxide.grant group default timecommand.use c.grant group default timecommand.use # Carbon
Variations
Each one opens the generator with a ready prompt you can tweak.
Show the time in an on-screen HUD
Swap the chat reply for a persistent CUI panel showing the same countdown.
Generate this versionConsole command for admins
Add a server-console variant so RCON users can query the clock too.
Generate this versionAnnounce day and night in chat
Broadcast a server-wide message when the cycle crosses sunrise or sunset.
Generate this version
FAQ
Why does my /time output disagree with the sun?
env.time is the authoritative hour value, but some custom maps run an unusual day length. Check env.daylength in the server console; the plugin uses it for the countdown, so it should be sane before you trust the output.
Does it work on Carbon?
Yes. Carbon loads Oxide plugins as-is, including the lang file and permission system.
To install: drop TimeCommand.cs into oxide/plugins, load it, then grant timecommand.use with oxide.grant user <name> timecommand.use.
How do I change the sunrise and sunset hours?
Edit SunriseHour and SunsetHour in oxide/config/TimeCommand.json, then reload the plugin. Values are whole hours in 24-hour format, so SunriseHour: 7 makes the countdown target 07:00 in-game time.
Can everyone use /time without a permission?
By default only holders of timecommand.use get a reply; other players get the NoPermission message. To open it up, grant the permission to the default group or remove the check in CmdTime.
Customize it for your server
Change the reward, add a cooldown, restrict it to VIPs. Start from this prompt and edit anything.
Edit the prompt on the next screen. 2 free plugins a month, no card.
More utility plugins
- Command aliasesThis plugin maps short chat commands like /h and /k to longer ones such as /home and /kit, defined entirely in its config file, using theโฆOnServerInitializedOnPlayerCommandOnPlayerDisconnected
- Info commandsA config-driven text command plugin: every entry in the config defines a command name and a list of chat lines, and the plugin registers them allโฆ
- Map grid locationA /grid chat command that converts a player's world position into a Rust map grid cell (like G12) using the server's world size, with exactโฆOnServerInitializedOnPlayerConnected
Last updated Oct 26, 2026.
Customize time command
Customize