travisjeffery/timecop
A gem providing "time travel", "time freezing", and "time acceleration" capabilities, making it simple to test time-dependent code. It provides a unified method to mock Time.now, Date.today, and DateTime.now in a single call.
Healthy across the board
Permissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 4w ago
- ✓28+ active contributors
- ✓Distributed ownership (top contributor 46% of recent commits)
Show 3 more →Show less
- ✓MIT licensed
- ✓CI configured
- ✓Tests present
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/travisjeffery/timecop)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/travisjeffery/timecop on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: travisjeffery/timecop
Generated by RepoPilot · 2026-05-10 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/travisjeffery/timecop shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across the board
- Last commit 4w ago
- 28+ active contributors
- Distributed ownership (top contributor 46% of recent commits)
- MIT licensed
- CI configured
- Tests present
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live travisjeffery/timecop
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/travisjeffery/timecop.
What it runs against: a local clone of travisjeffery/timecop — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in travisjeffery/timecop | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 57 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of travisjeffery/timecop. If you don't
# have one yet, run these first:
#
# git clone https://github.com/travisjeffery/timecop.git
# cd timecop
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of travisjeffery/timecop and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "travisjeffery/timecop(\\.git)?\\b" \\
&& ok "origin remote is travisjeffery/timecop" \\
|| miss "origin remote is not travisjeffery/timecop (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "lib/timecop.rb" \\
&& ok "lib/timecop.rb" \\
|| miss "missing critical file: lib/timecop.rb"
test -f "lib/timecop/timecop.rb" \\
&& ok "lib/timecop/timecop.rb" \\
|| miss "missing critical file: lib/timecop/timecop.rb"
test -f "lib/timecop/time_stack_item.rb" \\
&& ok "lib/timecop/time_stack_item.rb" \\
|| miss "missing critical file: lib/timecop/time_stack_item.rb"
test -f "lib/timecop/time_extensions.rb" \\
&& ok "lib/timecop/time_extensions.rb" \\
|| miss "missing critical file: lib/timecop/time_extensions.rb"
test -f "lib/timecop/version.rb" \\
&& ok "lib/timecop/version.rb" \\
|| miss "missing critical file: lib/timecop/version.rb"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 57 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~27d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/travisjeffery/timecop"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Timecop is a Ruby gem that mocks Time.now, Date.today, DateTime.now, and Process.clock_gettime globally across your codebase. It provides three core capabilities: freezing time to a specific instant (so time doesn't advance), traveling to a point in time (letting time advance from there), and scaling time to run at an accelerated pace. This eliminates test brittleness around time-dependent logic like expiration checks, billing cycles, or scheduled tasks. Simple gem structure: lib/timecop.rb is the entry point requiring lib/timecop/timecop.rb (core API), lib/timecop/time_extensions.rb (monkey-patches to Time/Date/DateTime), and lib/timecop/time_stack_item.rb (manages nested freeze/travel state). Tests in /test/ mirror functionality with separate test files for freeze vs. travel, Date.parse variants, and ActiveSupport integration.
👥Who it's for
Ruby developers (both vanilla Ruby and Ruby on Rails) who need to test time-sensitive business logic—specifically QA engineers and backend developers writing unit/integration tests for features like mortgage due dates, subscription expirations, cron job timing, or temporal data operations.
🌱Maturity & risk
Production-ready and stable. The gem has a clean CI pipeline (.github/workflows/CI.yml), comprehensive test suite covering date/time parsing edge cases and ActiveSupport integration (30+ test files), and active maintenance. No breaking changes visible in recent history. Widely used in Rails projects since its inception.
Low risk: zero external dependencies (noted in README), single maintainer (travisjeffery) but long-established with broad community adoption. Main concern is Ruby version compatibility—requires testing against multiple Ruby versions in CI. No recent major refactors evident, so adoption is safe but monitor for Rails 7.0+ compatibility.
Active areas of work
Maintenance mode: CI runs on every commit (GitHub Actions), Dependabot updates dependencies, and recent work focuses on Ruby/Rails version compatibility. No active feature development visible—repo is stable and well-tested.
🚀Get running
git clone https://github.com/travisjeffery/timecop.git
cd timecop
bundle install
bundle exec rake test
Daily commands:
bundle exec rake test # Run full test suite
bundle exec rake test TEST=test/timecop_test.rb # Single file
bin/console # Interactive Ruby shell with timecop loaded
🗺️Map of the codebase
lib/timecop.rb— Main entry point and public API for Timecop; all time-mocking operations flow through this module.lib/timecop/timecop.rb— Core implementation of freeze, travel, and scale operations; contains the central logic for time manipulation.lib/timecop/time_stack_item.rb— Represents a single time-mocking state on the stack; essential for understanding nested freeze/travel calls.lib/timecop/time_extensions.rb— Monkey-patches to Time, Date, and DateTime classes; where actual time interception happens.lib/timecop/version.rb— Version constant definition; referenced by gemspec and CI workflows.timecop.gemspec— Gem metadata and dependencies; defines what gets published and installed.
🧩Components & responsibilities
- Timecop (Public API) (Pure Ruby module with class variables and instance methods) — Exposes freeze, travel, scale, return, and time? methods; pushes/pops TimeStackItems and delegates time calculation.
- Failure mode: If Timecop.return is not called, time remains frozen globally; subsequent tests fail or behave unexpectedly.
- TimeStackItem (State Holder) (Ruby class with microsecond arithmetic) — Encapsulates a single mocking layer; calculates the current mocked time offset given the layer's type (freeze, travel, scale) and parameters.
- Failure mode: If send_microseconds calculation is incorrect, mocked time will be wrong for all code in the process.
- time_extensions (Interceptor) (Ruby refinements/reopening of standard library classes) — Monkey-patches Time.now, Date.today, DateTime.now, Process.clock_gettime to return mocked time by querying the Timecop stack.
- Failure mode: If a time method is not patched, code calling it will see real time instead of mocked time.
- Time Stack (State Manager) (Array-based stack stored as a class variable) — LIFO queue of TimeStackItems; maintains nesting order and provides peek/push/pop operations.
- Failure mode: If push/pop is unbalanced (e.g., more returns than freezes), stack underflows or mocking breaks unexpectedly.
🛠️How to make changes
Add support for a new time source or Ruby time method
- Identify the new method or time source to mock (e.g., a custom clock API or new Ruby stdlib method). (
lib/timecop/time_extensions.rb) - Add a monkey-patch in time_extensions.rb following the existing pattern (e.g., override Time.now, Date.today). (
lib/timecop/time_extensions.rb) - In the patched method, call Timecop.send_microseconds (or similar internal method) to get the current mocked time offset. (
lib/timecop/timecop.rb) - Add tests to verify the new method responds to freeze, travel, and scale operations. (
test/timecop_test.rb)
Modify the time-stacking mechanism (e.g., add a new operation mode)
- Define the new mode as a new type or class constant in TimeStackItem if needed. (
lib/timecop/time_stack_item.rb) - Extend the TimeStackItem#send_microseconds method to handle the new mode's calculation logic. (
lib/timecop/time_stack_item.rb) - Add a public method in Timecop module (e.g., accelerate) that pushes the new operation type onto the stack. (
lib/timecop/timecop.rb) - Write integration tests to verify nesting, return behavior, and interaction with existing modes. (
test/timecop_test.rb)
Add a new configuration option or API flag
- Add a class instance variable to Timecop module (e.g., @@config_option). (
lib/timecop/timecop.rb) - Create a public class method in the Timecop module to read/write the setting (e.g., Timecop.configure). (
lib/timecop/timecop.rb) - Reference the configuration variable from TimeStackItem or time_extensions.rb as needed. (
lib/timecop/time_extensions.rb) - Add tests demonstrating the configuration's effect on time mocking behavior. (
test/timecop_test.rb)
🔧Why these technologies
- Pure Ruby (no external dependencies) — Timecop must work with any Ruby project without adding dependency overhead; monkey-patching is the only viable mechanism to intercept time calls universally.
- Monkey-patching of Time, Date, DateTime, Process — Ruby's time classes are immutable and there is no built-in hook to override time sources; patching is the standard approach in Ruby testing utilities.
- Stack-based state machine (TimeStackItem LIFO) — Enables nested freeze/travel/scale calls to work correctly; each call layer can be independently popped, restoring prior time state.
⚖️Trade-offs already made
-
Global mutable state (the time_stack) managed at module level
- Why: Time is inherently global in any process; all code in the Ruby runtime sees the same Time.now. Thread-local state would fragment time.
- Consequence: Not thread-safe; concurrent threads will interfere with each other's mocked time. Users must synchronize manual Timecop calls or accept that parallel tests may fail unpredictably.
-
Monkey-patch Ruby standard library instead of using configuration or context objects
- Why: Existing code (gems, application code) expects Time.now to just work; no refactoring needed.
- Consequence: Timecop affects the entire process globally; cannot selectively mock time for specific code paths. Cleanup (Timecop.return) is mandatory or time remains frozen.
-
No dependency on ActiveSupport or Rails; standalone gem
- Why: Maximum compatibility with any Ruby project.
- Consequence: Timecop must separately handle Rails-specific time extensions (Time.zone, Time.current); separate optional tests validate this compatibility.
🚫Non-goals (don't propose these)
- Thread-safe mocking of time (time is global; concurrent tests require synchronization)
- Mocking of all possible Ruby time libraries (only Standard Library Time, Date, DateTime, and Process.clock_gettime)
- Selective/contextual time travel (all code in the process sees the same mocked time)
- Performance optimization for microsecond-precision time calculations (trades accuracy over speed)
🪤Traps & gotchas
- Process.clock_gettime mocking (mentioned in README but complex): requires careful handling in tests on different OS kernels—test/timecop_with_process_clock_test.rb shows gotchas. 2. Nested block semantics: Timecop.freeze/travel inside another freeze/travel block nests the time context; exiting the inner block restores outer context—easy to misunderstand without reading TimeStackItem. 3. ActiveSupport Time.zone conflict: When Rails is present, Time.zone.now is mocked independently; mixing Time.now and Time.zone.now in tests can cause confusion (see timecop_with_active_support_test.rb). 4. No teardown auto-cleanup: Tests must call Timecop.return explicitly or use blocks—forgetting this leaves time mocked for subsequent tests.
🏗️Architecture
💡Concepts to learn
- Time offset mocking — Timecop doesn't replace system time (which would require privileged kernel calls); instead it intercepts Ruby calls and returns offsets—understanding this distinction prevents misconceptions about what Timecop can/cannot do.
- Stack-based nested context — Timecop uses a TimeStack to support nested freeze/travel calls (each block pushes/pops its time context); this is why exiting an inner block restores the outer one's time state instead of reverting to real time.
- Monkey-patching in Ruby — Core mechanism: Timecop reopens Time, Date, DateTime classes to override their constructors and now/today methods; essential to understand how Timecop intercepts calls without requiring code changes in the app.
- Temporal testing — Timecop solves the 'moving parts problem' in testing: code that depends on current time is non-deterministic unless mocked—Timecop is the canonical Ruby solution for this class of test brittleness.
- Time scaling / simulated acceleration — Timecop.scale() makes time advance N times faster than real time; useful for integration tests that need to simulate hours/days passing but run in seconds—requires tracking scale factor in TimeStackItem.
- Coordinated Universal Time (UTC) vs. local time — Timecop must handle Time.now (local), Time.now.utc (UTC), and Rails Time.zone.now (configurable zone); test files like timecop_date_parse_scenarios.rb show complexity of parsing and normalizing across zones.
🔗Related repos
jeremyevans/timecop-travel— Alternative time-mocking gem with lighter API surface; shows design choice differences in how time state is managed.coinbase/bud— Provides time mocking alongside other test fixtures for Ruby; companion tool used alongside Timecop in larger test suites.rails/rails— Rails framework that integrates Timecop in test environments; see Rails testing guides for recommended Timecop setup patterns.rspec/rspec-rails— RSpec testing framework commonly used with Timecop in Rails projects; complementary testing tool.
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive test coverage for Process.clock_gettime mocking across different clock types
The README mentions that Timecop mocks Process.clock_gettime, but test/timecop_with_process_clock_test.rb likely only covers basic scenarios. Different clock types (CLOCK_REALTIME, CLOCK_MONOTONIC, CLOCK_PROCESS_CPUTIME_ID, etc.) have different behaviors and edge cases. A new contributor could expand test coverage to ensure all clock types are properly mocked during freeze/travel operations, preventing subtle bugs in production code that relies on specific clock types.
- [ ] Review existing test/timecop_with_process_clock_test.rb to identify gaps in clock type coverage
- [ ] Add test cases for at least CLOCK_REALTIME, CLOCK_MONOTONIC, and CLOCK_PROCESS_CPUTIME_ID
- [ ] Test interactions between time travel/freeze and each clock type
- [ ] Verify behavior with nested Timecop.travel/freeze blocks for each clock type
- [ ] Update test file with descriptive test names for each clock variant
Add integration tests for ActiveSupport time zone handling during time travel/freeze
test/timecop_with_active_support_test.rb exists but likely only covers basic cases. ActiveSupport's time zone support (Time.zone, Time.current, etc.) has complex interactions with Timecop, especially with DST transitions, timezone conversions, and in_time_zone blocks. A new contributor could add comprehensive tests covering these edge cases to prevent time zone-related bugs in Rails applications.
- [ ] Review existing test/timecop_with_active_support_test.rb for current coverage
- [ ] Add tests for DST boundary transitions (spring forward/fall back) with Timecop.freeze and Timecop.travel
- [ ] Test Time.zone.now behavior during travel across different timezones
- [ ] Add tests for nested in_time_zone blocks during frozen/traveled time
- [ ] Test behavior with ActiveSupport's time helpers (1.hour.from_now, etc.) during time manipulation
- [ ] Document any known limitations or gotchas in README.markdown
Refactor and expand Date.strptime/parse scenario tests into parameterized test suite
The repo has multiple date parsing test files (test/date_parse_scenarios.rb, test/date_strptime_scenarios.rb, test/date_strptime_year_boundary_scenarios.rb) with duplicated test structure. These could be consolidated into a single parameterized/data-driven test suite in lib/timecop that tests all parsing scenarios systematically. This improves maintainability, reduces code duplication, and makes it easier to add new scenarios without creating new test files.
- [ ] Analyze the structure and assertions in test/date_*_scenarios.rb files
- [ ] Create a test/scenarios directory with organized scenario data files (YAML or JSON)
- [ ] Implement a parameterized test helper in test/test_helper.rb that loads and runs scenarios
- [ ] Consolidate test/timecop_date_parse_freeze_test.rb, test/timecop_date_parse_travel_test.rb, and similar files to use the new parameterized approach
- [ ] Ensure all edge cases (year boundaries, leap years, DST transitions) are covered in the consolidated suite
- [ ] Update Rakefile/Makefile if necessary to run the refactored tests
🌿Good first issues
- Add docstring examples to lib/timecop/timecop.rb scale() method showing realistic time acceleration use cases (currently least-documented feature compared to freeze/travel).
- Create a test file test/timecop_timezone_edge_cases_test.rb covering freeze/travel across daylight-saving time transitions and multiple time zones—visible gap in current test coverage.
- Expand test/date_strptime_scenarios.rb with Date.strptime mocking tests for non-English locales (currently only handles English format strings).
⭐Top contributors
Click to expand
Top contributors
- @joshuacronemeyer — 46 commits
- @ptrela — 7 commits
- @jamiemccarthy — 5 commits
- @jwillemsen — 4 commits
- @dependabot[bot] — 3 commits
📝Recent commits
Click to expand
Recent commits
b3c9a5a— get ready for our next release 0.9.11 (#444) (joshuacronemeyer)4e84492— Fix Time.new keyword arguments on JRuby 10 (#443) (johnnyshields)b06405e— (434) strptime_with_mock_date :: Fix year boundary (#437) (jchapa)bb4a3c1— Fix time-only DateTime.parse under freeze (#440) (konieczkow)e4bba8c— Improve and fix CI (#439) (willnet)d695c6f— Require Ruby >= 2.1.0 (#423) (alexcwatt)ce3e6bb— Revert "Calculate travel_offset to align with the precision of argument to Ti…" (#430) (joshuacronemeyer)477ce9a— add ruby 3.3 to ci matrix (#429) (joshuacronemeyer)e05c2c0— Calculate travel_offset to align with the precision of argument to Timecop.travel (#421) (dgholz)e556094— new release (#428) (joshuacronemeyer)
🔒Security observations
The timecop gem codebase has a generally secure posture as a time-mocking testing library with no complex external dependencies or network operations. The main security concerns are Docker/infrastructure related: unpinned base image version, lack of version pinning for reproducible builds, and missing evidence of active security scanning in the CI pipeline. As a development/testing utility gem with no production data handling or user input processing, injection vulnerabilities are not a primary concern. Recommend pinning Ruby version in Dockerfile and enabling automated dependency vulnerability scanning via Dependabot and Bundler Audit.
- Medium · Dockerfile uses base image without version pinning —
Dockerfile. The Dockerfile uses 'FROM ruby' without specifying a version tag. This means the image will pull the latest Ruby version available, which could introduce unexpected breaking changes or security vulnerabilities in future builds. Using an unpinned base image makes builds non-deterministic and harder to reproduce. Fix: Pin the Ruby version explicitly, e.g., 'FROM ruby:3.2-alpine' or 'FROM ruby:3.2.0'. Use Alpine Linux variant for smaller attack surface. - Low · Missing .dockerignore entries —
.dockerignore. The .dockerignore file exists but its content is not shown. Common practice is to exclude unnecessary files (git history, test files, CI configs, development dependencies) from Docker builds to reduce image size and attack surface. Fix: Ensure .dockerignore contains: .git, .gitignore, .github, test/, spec/, .rspec, Makefile, .md, node_modules, vendor, .env - Low · Development dependencies may be included in Docker image —
Dockerfile. The Dockerfile copies Gemfile and runs 'bundle install' without specifying BUNDLE_WITHOUT to exclude development/test groups. This could include unnecessary gems with their own vulnerabilities in the production image. Fix: Use 'RUN bundle install --without development test' or configure Bundler to exclude non-production groups during Docker build. - Low · No security scanning in CI/CD pipeline visibility —
.github/workflows/CI.yml. The GitHub Actions CI workflow file (.github/workflows/CI.yml) is referenced but not shown. There is no visible evidence of dependency scanning, SAST, or security checks in the pipeline. Fix: Integrate security scanning tools: Dependabot (already configured), Bundler Audit for gem vulnerabilities, and SAST tools like Brakeman or Semgrep into the CI pipeline.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.