Skip to content
RustPluginGenerator

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

TimeCommand.cs
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.

TimeCommand.json
{
  "Sunrise hour (0-23)": 6,
  "Sunset hour (0-23)": 20
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
/timeShows the in-game time, the real UTC server time, and the countdown to the next sunrise or sunset.timecommand.use
Permissions
PermissionGrants
timecommand.useAllows a player to run the /time chat command.

How the code works

  1. Read the game clock from server variables

    TryReadClock calls ConsoleSystem.GetValue("env.time") to get the current hour and ConsoleSystem.GetValue("env.daylength") to get how many real minutes a full in-game day lasts. Both are strings, so the method parses them with float.TryParse. If env.time is missing or unparsable it returns false and the command replies with the Unavailable message. env.daylength falls back to 45 minutes if it is missing or zero.

  2. Normalize the hour and pick the next event

    TryReadClock wraps the parsed hour into 0..24 with %. CmdTime clamps SunriseHour and SunsetHour from 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 matching Sunrise or Sunset lang key as the label.

  3. Convert the fraction of a day into minutes

    The plugin computes hoursAhead as targetHour - 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). FormatMinutes turns the result into Xm or Xh Ym, and reports under a minute when nothing is left.

  4. Format the in-game clock

    FormatHour accepts a float hour and converts it to whole minutes with Mathf.RoundToInt(hour * 60f), wraps it into a single day with % (24 * 60), and formats it as HH:MM using string.Format. The current in-game time and the target sunrise or sunset hour both pass through it, so odd config values still print cleanly.

  5. Real server time

    The real-world time comes from DateTime.UtcNow.ToString("HH:mm") with UTC appended. DateTime.Now would 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.

  6. Permission and chat reply

    The [ChatCommand("time")] method CmdTime receives (BasePlayer player, string command, string[] args). It checks player != null and permission.UserHasPermission(player.UserIDString, PermUse), where PermUse is timecommand.use, registered in Init. The single reply goes through Msg("Reply", ...), so the lang file can translate or reword it without touching code.

  7. Config load and reload

    LoadDefaultConfig builds PluginConfig with SunriseHour 6 and SunsetHour 20. LoadConfig reads the file, warns and falls back to defaults if parsing fails, then calls SaveConfig. Edit oxide/config/TimeCommand.json and run oxide.reload TimeCommand to apply new hours.

Install it on your server

  1. Save TimeCommand.cs

    Keep the file name exactly as the class name. Oxide matches them.
  2. 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.
  3. Edit the config if you want

    On first load the default config is written to oxide/config/TimeCommand.json (Carbon: carbon/configs/TimeCommand.json). After editing, reload it:oxide.reload TimeCommand c.reload TimeCommand # Carbon
  4. Grant the permission

    Give it to every player, or swap default for a group such as vip: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 version
  • Console command for admins

    Add a server-console variant so RCON users can query the clock too.

    Generate this version
  • Announce 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.

Prompt: Write a Rust Oxide plugin called TimeCommand with a /time chat command (permission timecommand.use) that prints in-game time, UTC server time, and minutes until sunrise or sunset. Configurable sunrise hour 6 and sunset hour 20.
Customize for my server

Edit the prompt on the next screen. 2 free plugins a month, no card.

Last updated Oct 26, 2026.

Customize time command

Customize