| Crates.io | bevy_ingame_clock |
| lib.rs | bevy_ingame_clock |
| version | 0.2.1 |
| created_at | 2025-10-19 09:51:11.122983+00 |
| updated_at | 2025-11-30 21:14:44.118114+00 |
| description | An in-game clock plugin for the Bevy game engine |
| homepage | |
| repository | https://github.com/don-matheo/bevy_ingame_clock |
| max_upload_size | |
| id | 1890290 |
| size | 445,219 |
A plugin for the Bevy game engine that provides an in-game clock system with date/time tracking, configurable speed, sending events on time based intervals, and flexible formatting.

| Bevy Version | Plugin Version |
|---|---|
| 0.17 | 0.2 |
Add this to your Cargo.toml:
[dependencies]
bevy = "0.17"
bevy_ingame_clock = "0.2"
use bevy::prelude::*;
use bevy_ingame_clock::{InGameClockPlugin, InGameClock};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(InGameClockPlugin)
.add_systems(Update, display_time)
.run();
}
fn display_time(clock: Res<InGameClock>) {
println!("In-game datetime: {}", clock.format_datetime(None));
}
use bevy_ingame_clock::InGameClock;
fn setup(mut commands: Commands) {
// Default: uses current UTC date/time
commands.insert_resource(InGameClock::new());
// Custom start date/time: June 15, 2024 at 8:00:00 AM
commands.insert_resource(
InGameClock::with_start_datetime(2024, 6, 15, 8, 0, 0)
);
}
fn setup_speed(mut clock: ResMut<InGameClock>) {
// Method 1: Direct speed multiplier
clock.set_speed(2.0); // Time passes 2x faster
// Method 2: Set by day duration (more intuitive for game design)
// One in-game day passes every 60 real seconds (1 minute)
clock.set_day_duration(60.0);
// One in-game day passes every 1200 real seconds (20 minutes)
clock.set_day_duration(1200.0);
// Get current day duration
let duration = clock.day_duration();
println!("One day takes {} real seconds", duration);
}
fn setup(mut commands: Commands) {
commands.insert_resource(
InGameClock::with_start_datetime(2024, 6, 15, 8, 0, 0)
.with_speed(10.0) // Or use .with_day_duration(120.0)
);
}
fn toggle_pause_system(
mut clock: ResMut<InGameClock>,
keyboard: Res<ButtonInput<KeyCode>>,
) {
if keyboard.just_pressed(KeyCode::Space) {
clock.toggle_pause();
}
}
fn check_datetime(clock: Res<InGameClock>) {
// Get formatted strings (default formats)
let datetime = clock.format_datetime(None); // "2024-06-15 14:30:45"
let date = clock.format_date(None); // "2024-06-15"
let time = clock.format_time(None); // "14:30:45"
// Get individual components
let (year, month, day) = clock.current_date();
let (hour, minute, second) = clock.current_time();
println!("{}-{:02}-{:02} {:02}:{:02}:{:02}", year, month, day, hour, minute, second);
// Get as chrono NaiveDateTime for advanced operations
let dt = clock.current_datetime();
println!("Day of week: {}", dt.weekday());
}
fn custom_formats(clock: Res<InGameClock>) {
// Custom date formats
clock.format_date(Some("%d/%m/%Y")); // "15/06/2024"
clock.format_date(Some("%B %d, %Y")); // "June 15, 2024"
clock.format_date(Some("%A, %B %d, %Y")); // "Saturday, June 15, 2024"
// Custom time formats
clock.format_time(Some("%I:%M %p")); // "02:30 PM"
clock.format_time(Some("%H:%M")); // "14:30"
// Custom datetime formats
clock.format_datetime(Some("%d/%m/%Y %H:%M")); // "15/06/2024 14:30"
clock.format_datetime(Some("%B %d, %Y at %I:%M %p")); // "June 15, 2024 at 02:30 PM"
clock.format_datetime(Some("%c")); // Locale-specific format
}
Format Specifiers (via chrono):
%Y - Year (4 digits)%m - Month (01-12)%d - Day (01-31)%H - Hour 24h (00-23)%I - Hour 12h (01-12)%M - Minute (00-59)%S - Second (00-59)%p - AM/PM%B - Full month name%A - Full weekday name%E - Epoch name (for custom calendars only)The event system allows you to receive Bevy messages at specific in-game time intervals.
use bevy_ingame_clock::{ClockCommands, ClockInterval, ClockIntervalEvent};
fn setup(mut commands: Commands) {
// Register intervals to receive events
commands.register_clock_interval(ClockInterval::Hour);
commands.register_clock_interval(ClockInterval::Day);
// Custom interval: every 90 seconds
commands.register_clock_interval(ClockInterval::Custom(90));
// Registering the same interval multiple times is safe - duplicates are automatically prevented
commands.register_clock_interval(ClockInterval::Hour); // This is silently ignored
}
fn handle_events(mut events: MessageReader<ClockIntervalEvent>) {
for event in events.read() {
match event.interval {
ClockInterval::Hour => println!("An hour passed! (count: {})", event.count),
ClockInterval::Day => println!("A day passed! (count: {})", event.count),
ClockInterval::Custom(seconds) => {
println!("Custom interval of {} seconds passed!", seconds);
},
_ => {}
}
}
}
How It Works:
count field tracking total occurrences since the clock startedAvailable Intervals:
ClockInterval::Second - Every in-game secondClockInterval::Minute - Every 60 in-game secondsClockInterval::Hour - Every hour (duration depends on calendar: 3600s for Gregorian, configurable for custom calendars)ClockInterval::Day - Every day (duration depends on calendar: 86400s for Gregorian, configurable for custom calendars)ClockInterval::Week - Every week (duration depends on calendar: 604800s for Gregorian, configurable for custom calendars)ClockInterval::Custom(seconds) - Custom interval in secondsNote: When using custom calendars, the Hour, Day, and Week intervals automatically adjust to match the calendar's configured time units. For example, with a 20-hour day, the Day interval fires every 72000 seconds instead of 86400.
The plugin supports custom calendar systems for fantasy or sci-fi games with non-Gregorian time structures.
You can create custom calendars in two ways:
1. Builder Pattern (Programmatic)
Create calendars directly in code using the CustomCalendarBuilder:
use bevy_ingame_clock::{CustomCalendarBuilder, Month, Epoch};
fn setup(mut commands: Commands) {
let fantasy_calendar = CustomCalendarBuilder::new()
.minutes_per_hour(60)
.hours_per_day(20)
.months(vec![
Month::new("Frostmoon", 20, 3),
Month::new("Thawmoon", 21, 0),
Month::new("Bloomtide", 19, 2),
])
.weekdays(vec![
"Moonday".to_string(),
"Fireday".to_string(),
"Waterday".to_string(),
"Earthday".to_string(),
"Starday".to_string(),
])
.leap_years("# % 2 == 0")
.epoch(Epoch::new("Age of Magic", 1000))
.build();
let clock = InGameClock::new()
.with_calendar(fantasy_calendar)
.with_day_duration(60.0);
commands.insert_resource(clock);
}
2. RON Configuration Files
Load calendars from configuration files for easier editing by designers:
use bevy_ingame_clock::{CustomCalendar, InGameClock};
use std::fs;
fn setup(mut commands: Commands) {
let calendar_config = fs::read_to_string("assets/fantasy_calendar.ron")
.expect("Failed to read calendar file");
let fantasy_calendar: CustomCalendar = ron::from_str(&calendar_config)
.expect("Failed to parse calendar file");
let clock = InGameClock::new()
.with_calendar(fantasy_calendar)
.with_day_duration(60.0);
commands.insert_resource(clock);
}
Example RON file (assets/fantasy_calendar.ron):
(
minutes_per_hour: 60,
hours_per_day: 20,
months: [
(name: "Frostmoon", days: 20, leap_days: 3),
(name: "Thawmoon", days: 21, leap_days: 0),
(name: "Bloomtide", days: 19, leap_days: 2),
],
weekdays: ["Moonday", "Fireday", "Waterday", "Earthday", "Starday"],
leap_years: "# % 2 == 0",
epoch: (name: "Age of Magic", start_year: 1000),
)
Which Approach to Use?
Both approaches create identical CustomCalendar instances and work seamlessly with the same API.
minutes_per_hour: Number of minutes in an hourhours_per_day: Number of hours in a dayleap_years: Leap year expression - a boolean expression using # as the year placeholder (see Leap Year System below)months: Array of month definitions, each with:
name: Month namedays: Base number of days in the monthleap_days: Additional days added during leap years (allows distributing leap days across months)weekdays: Names for each day of the week. The number of weekday names determines the days per week. The first name in the list is day 0 of the weekepoch: Epoch definition with:
name: Name of the epoch (e.g., "Age of Magic", "Common Era")start_year: Starting year for the calendar systemLeap Year System:
The leap year system uses boolean expressions to define when leap years occur. The leap_years field accepts a string expression using # as a placeholder for the year value.
Expression Syntax:
Expressions use the variable year and support:
+, -, *, /, % (modulo)==, !=, <, >, <=, >=&& (and), || (or), ! (not)(, )# as the year placeholderExamples:
// RON file examples:
leap_years: "# % 4 == 0" // Every 4 years
leap_years: "# % 2 == 0" // Every 2 years
leap_years: "# % 4 == 0 && (# % 100 != 0 || # % 400 == 0)" // Gregorian rule
leap_years: "(# % 3 == 0 && # % 9 != 0) || # % 27 == 0" // Complex custom rule
leap_years: "false" // No leap years
Leap Day Distribution:
Each month can specify leap_days - extra days added during leap years. This allows you to distribute leap days across multiple months or concentrate them in specific months, unlike the Gregorian calendar which adds all leap days to one month.
Total Year Length:
In a normal year, the year length is the sum of all month days. In a leap year, it's the sum of all (days + leap_days).
Example: In the fantasy calendar above with leap_years: "# % 2 == 0":
For more examples, see the examples/custom_calendar.rs file and examples/fantasy_calendar.ron configuration.
InGameClockPluginThe main plugin. Add it to your Bevy app to enable clock functionality.
InGameClock Resource| Field | Type | Description |
|---|---|---|
elapsed_seconds |
f64 |
Total in-game time elapsed in seconds since start |
speed |
f32 |
Speed multiplier (1.0 = real-time, 2.0 = double speed) |
paused |
bool |
Whether the clock is paused |
start_datetime |
NaiveDateTime |
The starting date/time for the clock |
new() - Create a new clock with current UTC date/timewith_start_datetime(year, month, day, hour, minute, second) - Set specific start date/timewith_start(datetime) - Set start from a NaiveDateTimewith_speed(speed) - Set initial speed multiplierwith_day_duration(real_seconds_per_day) - Set speed by defining real seconds per in-game daypause() - Pause the clockresume() - Resume the clocktoggle_pause() - Toggle pause stateset_speed(speed) - Change the clock speed multiplierset_day_duration(real_seconds_per_day) - Change speed by day durationday_duration() - Get current day duration in real secondscurrent_datetime() - Get current NaiveDateTime (use chrono traits for advanced operations)current_date() - Get current date as (year, month, day)current_time() - Get current time as (hour, minute, second)as_hms() - Get time as (hours, minutes, seconds) tupleformat_datetime(format) - Format date and time (default: "YYYY-MM-DD HH:MM:SS")format_date(format) - Format date only (default: "YYYY-MM-DD")format_time(format) - Format time only (default: "HH:MM:SS")All formatting methods accept Option<&str> where None uses the default format, or Some("format_string") for custom chrono format strings.
ClockIntervalEventMessage sent when a registered time interval has passed.
Fields:
interval: ClockInterval - The interval that triggered the eventcount: u64 - Total number of times this interval has passedClockInterval EnumDefines time intervals for events:
Second, Minute, Hour, Day, Week - Built-in intervalsCustom(u32) - Custom interval in secondsClockCommands TraitExtension trait for Commands to register intervals:
register_clock_interval(interval) - Register an interval to receive eventsRun the basic example with interactive controls:
cargo run --example basic
Interactive demo showing clock controls and display.
Controls:
Space - Pause/Resume+/- - Double/Halve speed1-6 - Set day duration (30s, 60s, 5min, 10min, 20min, real-time)R - Reset clockcargo run --example events
Demonstrates the interval event system with multiple registered intervals.
Controls:
Space - Pause/Resume+/- - Speed Up/Down1-6 - Toggle different interval events ON/OFFR - Reset clockcargo run --example digital_clock
Visual digital clock display, showing time in digital format with a date calendar display.

Controls:
Space - Pause/Resume+/- - Speed Up/DownR - Reset clockcargo run --example custom_calendar
Demonstrates a custom fantasy calendar system loaded from a RON configuration file with:
Controls:
Space - Pause/Resume+/- - Speed Up/DownR - Reset clockcargo run --example custom_calendar_builder
Demonstrates programmatically creating a custom sci-fi calendar using the CustomCalendarBuilder pattern:
# % 4 == 0 && (# % 100 != 0 || # % 400 == 0)Controls:
Space - Pause/Resume+/- - Speed Up/DownR - Reset clockLicensed under either of
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.