├── .dockerignore ├── .github ├── dependabot.yml └── workflows │ └── CI.yml ├── .gitignore ├── Dockerfile ├── Gemfile ├── History.md ├── LICENSE ├── Makefile ├── README.markdown ├── Rakefile ├── bin └── console ├── lib ├── timecop.rb └── timecop │ ├── time_extensions.rb │ ├── time_stack_item.rb │ ├── timecop.rb │ └── version.rb ├── test ├── date_parse_scenarios.rb ├── date_strptime_scenarios.rb ├── date_time_parse_scenarios.rb ├── test_helper.rb ├── time_stack_item_test.rb ├── timecop_date_parse_freeze_test.rb ├── timecop_date_parse_travel_test.rb ├── timecop_date_strptime_freeze_test.rb ├── timecop_date_strptime_travel_test.rb ├── timecop_test.rb ├── timecop_with_active_support_test.rb ├── timecop_with_process_clock_test.rb ├── timecop_without_date_but_with_time_test.rb └── timecop_without_date_test.rb └── timecop.gemspec /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Set update schedule for GitHub Actions 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | 9 | - package-ecosystem: bundler 10 | directory: "/" 11 | schedule: 12 | interval: weekly 13 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | schedule: 9 | - cron: '0 1 * * SUN' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | ruby: 18 | - head 19 | - '3.3' 20 | - '3.2' 21 | - '3.1' 22 | - '3.0' 23 | - '2.7' 24 | - '2.6' 25 | - '2.5' 26 | - '2.4' 27 | - '2.3' 28 | - jruby 29 | - truffleruby-head 30 | continue-on-error: ${{ matrix.ruby == 'head' }} 31 | name: Ruby ${{ matrix.ruby }} 32 | steps: 33 | - uses: actions/checkout@v4 34 | - uses: ruby/setup-ruby@v1 35 | with: 36 | ruby-version: ${{ matrix.ruby }} 37 | bundler-cache: true 38 | cache-version: 1 39 | - run: | 40 | bundle exec rake 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .ruby-version 3 | Gemfile.lock 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby 2 | 3 | WORKDIR /timecop 4 | 5 | COPY Gemfile . 6 | COPY timecop.gemspec . 7 | COPY lib/timecop/version.rb lib/timecop/version.rb 8 | 9 | RUN bundle -j 4 10 | 11 | COPY . . 12 | 13 | CMD ["bin/console"] 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | group :development do 6 | gem 'rake' 7 | gem 'nokogiri' 8 | gem 'pry' 9 | gem 'mocha' 10 | gem 'activesupport' 11 | gem 'tzinfo' 12 | gem 'i18n' 13 | gem 'minitest' 14 | end 15 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | # History 2 | 3 | ## Unreleased 4 | 5 | ## v0.9.10 6 | 7 | - Make Process.clock_gettime configurable and turned off by default (for backwards compatability) ([#427](https://github.com/travisjeffery/timecop/pull/427)) 8 | 9 | ## v0.9.9 10 | 11 | - Add `travelled?` and `scaled?` methods to allow checking if Timecop is in their respective states ([#414](https://github.com/travisjeffery/timecop/pull/414)) 12 | - Fix cases with DateTime parse not working right ([#415](https://github.com/travisjeffery/timecop/pull/415)) 13 | - Fix another case where DateTime parse not working right ([#417](https://github.com/travisjeffery/timecop/pull/417)) 14 | - Support travel and freeze for Process.clock_gettime ([#419](https://github.com/travisjeffery/timecop/pull/419)) 15 | 16 | ## v0.9.8 17 | 18 | - Revert Reduce memory usage ([#404](https://github.com/travisjeffery/timecop/pull/404)) 19 | - More better support for commercial and week-based date formats([#408](https://github.com/travisjeffery/timecop/pull/408)) 20 | 21 | ## v0.9.7 22 | 23 | - Fix `frozen?` to return false when traveling or scaled (TKTK) 24 | - Reduce memory usage ([#404](https://github.com/travisjeffery/timecop/pull/404)) 25 | - Better support for commercial and week-based date formats([#406](https://github.com/travisjeffery/timecop/pull/406)) 26 | 27 | ## v0.9.6 28 | 29 | - Support other calendars besides the default ([#389](https://github.com/travisjeffery/timecop/pull/389)) 30 | 31 | ## v0.9.5 32 | 33 | - Date#strptime Fix %Y issues ([#379](https://github.com/travisjeffery/timecop/pull/379)) 34 | - Add Truffleruby support ([#378](https://github.com/travisjeffery/timecop/pull/378)) 35 | - Deprecate ruby 2.5 ([#375](https://github.com/travisjeffery/timecop/pull/375)) 36 | - Better encapsulation of singleton instance ([#368](https://github.com/travisjeffery/timecop/pull/368)) 37 | - Support specifying only dates in Date.parse and Datetime.parse ([#296](https://github.com/travisjeffery/timecop/pull/296)) 38 | 39 | ## v0.9.4 40 | 41 | - Add ruby 3.1 support (#288) 42 | 43 | ## v0.9.3 44 | 45 | - Fix returning to previous date after block when `Timecop.thread_safe = true` (#216) 46 | - Fix %s and %Q for Date strptime (#275) 47 | - Fix '%G-%V' for Date strptime (#261) 48 | - Fix default day for strptime (#266) 49 | 50 | ## v0.9.2 51 | 52 | - Make `require 'timecop'` threadsafe (#239) 53 | 54 | ## v0.9.1 55 | 56 | - fix Date.parse when month/year only given (#206) 57 | 58 | ## v0.9.0 59 | 60 | - add thread_safe option (#184) 61 | - in safe mode, when already frozen, allow traveling with no block (#202) 62 | - using Time.travel with a string should AS' Time.zone class (#170) 63 | - fix Timecop being ignored when Date.parse infers year (#171, #158, #133) 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2019 — Travis Jeffery, John Trupiano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | IMAGE_NAME=timecop 2 | VOLUMES=-v $(PWD):/tmp/src 3 | 4 | image: 5 | docker build -t $(IMAGE_NAME) . 6 | 7 | clean: 8 | rm Gemfile.lock 9 | 10 | test: image 11 | docker run $(IMAGE_NAME) rake 12 | 13 | console: image 14 | docker run -it --rm -w /tmp/src $(VOLUMES) $(IMAGE_NAME) 15 | 16 | shell: image 17 | docker run -it --rm -w /tmp/src $(VOLUMES) $(IMAGE_NAME) bash 18 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # timecop 2 | 3 | [![Gem Version](https://badge.fury.io/rb/timecop.svg)](https://rubygems.org/gems/timecop) 4 | [![Build Status](https://github.com/travisjeffery/timecop/workflows/CI/badge.svg)](https://github.com/travisjeffery/timecop/actions?query=workflow%3ACI) 5 | 6 | ## DESCRIPTION 7 | 8 | A gem providing "time travel" and "time freezing" capabilities, making it dead simple to test time-dependent code. It provides a unified method to mock `Time.now`, `Date.today`, `DateTime.now`, and `Process.clock_gettime` in a single call. 9 | 10 | ## INSTALL 11 | 12 | `bundle add timecop` 13 | 14 | ## FEATURES 15 | 16 | - Freeze time to a specific point. 17 | - Travel back to a specific point in time, but allow time to continue moving forward from there. 18 | - Scale time by a given scaling factor that will cause time to move at an accelerated pace. 19 | - No dependencies, can be used with _any_ ruby project 20 | - Timecop api allows arguments to be passed into `#freeze` and `#travel` as one of the following: 21 | - Time instance 22 | - DateTime instance 23 | - Date instance 24 | - individual arguments (year, month, day, hour, minute, second) 25 | - a single integer argument that is interpreted as an offset in seconds from `Time.now` 26 | - Nested calls to `Timecop#travel` and `Timecop#freeze` are supported -- each block will maintain its interpretation of now. 27 | - Works with regular Ruby projects, and Ruby on Rails projects 28 | 29 | ## USAGE 30 | 31 | Run a time-sensitive test 32 | 33 | ```ruby 34 | joe = User.find(1) 35 | joe.purchase_home() 36 | assert !joe.mortgage_due? 37 | # move ahead a month and assert that the mortgage is due 38 | Timecop.freeze(Date.today + 30) do 39 | assert joe.mortgage_due? 40 | end 41 | ``` 42 | 43 | You can mock the time for a set of tests easily via setup/teardown methods 44 | 45 | ```ruby 46 | describe "some set of tests to mock" do 47 | before do 48 | Timecop.freeze(Time.local(1990)) 49 | end 50 | 51 | after do 52 | Timecop.return 53 | end 54 | 55 | it "should do blah blah blah" do 56 | end 57 | end 58 | ``` 59 | 60 | Set the time for the test environment of a rails app -- this is particularly 61 | helpful if your whole application is time-sensitive. It allows you to build 62 | your test data at a single point in time, and to move in/out of that time as 63 | appropriate (within your tests) 64 | 65 | in `config/environments/test.rb` 66 | 67 | ```ruby 68 | config.after_initialize do 69 | # Set Time.now to September 1, 2008 10:05:00 AM (at this instant), but allow it to move forward 70 | t = Time.local(2008, 9, 1, 10, 5, 0) 71 | Timecop.travel(t) 72 | end 73 | ``` 74 | 75 | ### The difference between Timecop.freeze and Timecop.travel 76 | 77 | `freeze` is used to statically mock the concept of now. As your program executes, 78 | `Time.now` will not change unless you make subsequent calls into the Timecop API. 79 | `travel`, on the other hand, computes an offset between what we currently think 80 | `Time.now` is (recall that we support nested traveling) and the time passed in. 81 | It uses this offset to simulate the passage of time. To demonstrate, consider 82 | the following code snippets: 83 | 84 | ```ruby 85 | new_time = Time.local(2008, 9, 1, 12, 0, 0) 86 | Timecop.freeze(new_time) 87 | sleep(10) 88 | new_time == Time.now # ==> true 89 | 90 | Timecop.return # "turn off" Timecop 91 | Timecop.travel(new_time) 92 | sleep(10) 93 | new_time == Time.now # ==> false 94 | ``` 95 | 96 | ### Timecop.scale 97 | 98 | Let's say you want to test a "live" integration wherein entire days could pass by 99 | in minutes while you're able to simulate "real" activity. For example, one such use case 100 | is being able to test reports and invoices that run in 30 day cycles in very little time, while also 101 | being able to simulate activity via subsequent calls to your application. 102 | 103 | ```ruby 104 | # seconds will now seem like hours 105 | Timecop.scale(3600) 106 | Time.now 107 | # => 2012-09-20 21:23:25 -0500 108 | # seconds later, hours have passed and it's gone from 9pm at night to 6am in the morning 109 | Time.now 110 | # => 2012-09-21 06:22:59 -0500 111 | ``` 112 | 113 | See [#42](https://github.com/travisjeffery/timecop/pull/42) for more information, thanks to Ken Mayer, David Holcomb, and Pivotal Labs. 114 | 115 | ### Timecop.safe_mode 116 | 117 | Safe mode forces you to use Timecop with the block syntax since it always puts time back the way it was. If you are running in safe mode and use Timecop without the block syntax `Timecop::SafeModeException` will be raised to tell the user they are not being safe. 118 | 119 | ``` ruby 120 | # turn on safe mode 121 | Timecop.safe_mode = true 122 | 123 | # check if you are in safe mode 124 | Timecop.safe_mode? 125 | # => true 126 | 127 | # using method without block 128 | Timecop.freeze 129 | # => Timecop::SafeModeException: Safe mode is enabled, only calls passing a block are allowed. 130 | ``` 131 | 132 | ### Configuring Mocking Process.clock_gettime 133 | 134 | By default Timecop does not mock Process.clock_gettime. You must enable it like this: 135 | 136 | ``` ruby 137 | # turn on 138 | Timecop.mock_process_clock = true 139 | ``` 140 | 141 | ### Rails v Ruby Date/Time libraries 142 | 143 | Sometimes [Rails Date/Time methods don't play nicely with Ruby Date/Time methods.](https://rails.lighthouseapp.com/projects/8994/tickets/6410-dateyesterday-datetoday) 144 | 145 | Be careful mixing Ruby `Date.today` with Rails `Date.tomorrow` / `Date.yesterday` as things might break. 146 | 147 | ## Contribute 148 | 149 | timecop is maintained by [travisjeffery](http://github.com/travisjeffery), and 150 | was created by [jtrupiano](https://github.com/jtrupiano). 151 | 152 | Here's the most direct way to get your work merged into the project. 153 | 154 | - Fork the project 155 | - Clone down your fork 156 | - Create a feature branch 157 | - Hack away and add tests, not necessarily in that order 158 | - Make sure everything still passes by running tests 159 | - If necessary, rebase your commits into logical chunks without errors 160 | - Push the branch up to your fork 161 | - Send a pull request for your branch 162 | 163 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'bundler/gem_tasks' 3 | require 'rake/testtask' 4 | require 'rdoc/task' 5 | 6 | Rake::RDocTask.new do |rdoc| 7 | if File.exist?('VERSION') 8 | version = File.read('VERSION') 9 | else 10 | version = "" 11 | end 12 | 13 | rdoc.rdoc_dir = 'rdoc' 14 | rdoc.options << '--line-numbers' << '--inline-source' 15 | rdoc.title = "timecop #{version}" 16 | rdoc.rdoc_files.include('README*') 17 | rdoc.rdoc_files.include('History.rdoc') 18 | rdoc.rdoc_files.include('lib/**/*.rb') 19 | end 20 | 21 | task :test do 22 | failed = Dir["test/*_test.rb"].map do |test| 23 | command = "ruby #{test}" 24 | puts 25 | puts command 26 | command unless system(command) 27 | end.compact 28 | if failed.any? 29 | abort "#{failed.count} Tests failed\n#{failed.join("\n")}" 30 | end 31 | end 32 | 33 | desc 'Default: run tests' 34 | task :default => [:test] 35 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | require "timecop" 6 | 7 | # You can add fixtures and/or initialization code here to make experimenting 8 | # with your gem easier. You can also use a different console, if you like. 9 | 10 | # (If you use this, don't forget to add pry to your Gemfile!) 11 | # require "pry" 12 | # Pry.start 13 | 14 | require "irb" 15 | IRB.start(__FILE__) 16 | -------------------------------------------------------------------------------- /lib/timecop.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), "timecop", "timecop") 2 | require File.join(File.dirname(__FILE__), "timecop", "version") 3 | -------------------------------------------------------------------------------- /lib/timecop/time_extensions.rb: -------------------------------------------------------------------------------- 1 | require 'time' 2 | require 'date' 3 | 4 | class Time #:nodoc: 5 | class << self 6 | def mock_time 7 | mocked_time_stack_item = Timecop.top_stack_item 8 | mocked_time_stack_item.nil? ? nil : mocked_time_stack_item.time(self) 9 | end 10 | 11 | alias_method :now_without_mock_time, :now 12 | 13 | def now_with_mock_time 14 | mock_time || now_without_mock_time 15 | end 16 | 17 | alias_method :now, :now_with_mock_time 18 | 19 | alias_method :new_without_mock_time, :new 20 | 21 | def new_with_mock_time(*args) 22 | args.size <= 0 ? now : new_without_mock_time(*args) 23 | end 24 | 25 | ruby2_keywords :new_with_mock_time if Module.private_method_defined?(:ruby2_keywords) 26 | 27 | alias_method :new, :new_with_mock_time 28 | end 29 | end 30 | 31 | class Date #:nodoc: 32 | class << self 33 | def mock_date 34 | mocked_time_stack_item.nil? ? nil : mocked_time_stack_item.date(self) 35 | end 36 | 37 | alias_method :today_without_mock_date, :today 38 | 39 | def today_with_mock_date 40 | mock_date || today_without_mock_date 41 | end 42 | 43 | alias_method :today, :today_with_mock_date 44 | 45 | alias_method :strptime_without_mock_date, :strptime 46 | 47 | def strptime_with_mock_date(str = '-4712-01-01', fmt = '%F', start = Date::ITALY) 48 | #If date is not valid the following line raises 49 | Date.strptime_without_mock_date(str, fmt, start) 50 | 51 | d = Date._strptime(str, fmt) 52 | now = Time.now.to_date 53 | year = d[:year] || d[:cwyear] || now.year 54 | mon = d[:mon] || now.mon 55 | if d.keys == [:year] 56 | Date.new(year, 1, 1, start) 57 | elsif d[:mday] 58 | Date.new(year, mon, d[:mday], start) 59 | elsif d[:yday] 60 | Date.new(year, 1, 1, start).next_day(d[:yday] - 1) 61 | elsif d[:cwyear] || d[:cweek] || d[:wnum0] || d[:wnum1] || d[:wday] || d[:cwday] 62 | week = d[:cweek] || d[:wnum1] || d[:wnum0] || now.strftime('%W').to_i 63 | if d[:wnum0] #Week of year where week starts on sunday 64 | if d[:cwday] #monday based day of week 65 | Date.strptime_without_mock_date("#{year} #{week} #{d[:cwday]}", '%Y %U %u', start) 66 | else 67 | Date.strptime_without_mock_date("#{year} #{week} #{d[:wday] || 0}", '%Y %U %w', start) 68 | end 69 | else #Week of year where week starts on monday 70 | if d[:wday] #sunday based day of week 71 | Date.strptime_without_mock_date("#{year} #{week} #{d[:wday]}", '%Y %W %w', start) 72 | else 73 | Date.strptime_without_mock_date("#{year} #{week} #{d[:cwday] || 1}", '%Y %W %u', start) 74 | end 75 | end 76 | elsif d[:seconds] 77 | Time.at(d[:seconds]).to_date 78 | else 79 | Date.new(year, mon, 1, start) 80 | end 81 | end 82 | 83 | alias_method :strptime, :strptime_with_mock_date 84 | 85 | def parse_with_mock_date(*args) 86 | parsed_date = parse_without_mock_date(*args) 87 | return parsed_date unless mocked_time_stack_item 88 | date_hash = Date._parse(*args) 89 | 90 | case 91 | when date_hash[:year] && date_hash[:mon] 92 | parsed_date 93 | when date_hash[:mon] && date_hash[:mday] 94 | Date.new(mocked_time_stack_item.year, date_hash[:mon], date_hash[:mday]) 95 | when date_hash[:mday] 96 | Date.new(mocked_time_stack_item.year, mocked_time_stack_item.month, date_hash[:mday]) 97 | when date_hash[:wday] 98 | closest_wday(date_hash[:wday]) 99 | else 100 | parsed_date + mocked_time_stack_item.travel_offset_days 101 | end 102 | end 103 | 104 | alias_method :parse_without_mock_date, :parse 105 | alias_method :parse, :parse_with_mock_date 106 | 107 | def mocked_time_stack_item 108 | Timecop.top_stack_item 109 | end 110 | 111 | def closest_wday(wday) 112 | today = Date.today 113 | result = today - today.wday 114 | result += 1 until wday == result.wday 115 | result 116 | end 117 | end 118 | end 119 | 120 | class DateTime #:nodoc: 121 | class << self 122 | def mock_time 123 | mocked_time_stack_item.nil? ? nil : mocked_time_stack_item.datetime(self) 124 | end 125 | 126 | def now_with_mock_time 127 | mock_time || now_without_mock_time 128 | end 129 | 130 | alias_method :now_without_mock_time, :now 131 | 132 | alias_method :now, :now_with_mock_time 133 | 134 | def parse_with_mock_date(*args) 135 | parsed_date = parse_without_mock_date(*args) 136 | return parsed_date unless mocked_time_stack_item 137 | date_hash = DateTime._parse(*args) 138 | 139 | case 140 | when date_hash[:year] && date_hash[:mon] 141 | parsed_date 142 | when date_hash[:mon] && date_hash[:mday] 143 | DateTime.new(mocked_time_stack_item.year, date_hash[:mon], date_hash[:mday]) 144 | when date_hash[:mday] 145 | DateTime.new(mocked_time_stack_item.year, mocked_time_stack_item.month, date_hash[:mday]) 146 | when date_hash[:wday] && date_hash[:hour] && date_hash[:min] 147 | closest_date = Date.closest_wday(date_hash[:wday]).to_datetime 148 | 149 | DateTime.new( 150 | closest_date.year, closest_date.month, closest_date.day, 151 | date_hash[:hour], date_hash[:min] 152 | ) 153 | when date_hash[:wday] 154 | Date.closest_wday(date_hash[:wday]).to_datetime 155 | when date_hash[:hour] && date_hash[:min] && date_hash[:sec] 156 | DateTime.new(mocked_time_stack_item.year, mocked_time_stack_item.month, mocked_time_stack_item.day, date_hash[:hour], date_hash[:min], date_hash[:sec]) 157 | else 158 | parsed_date + mocked_time_stack_item.travel_offset_days 159 | end 160 | end 161 | 162 | alias_method :parse_without_mock_date, :parse 163 | alias_method :parse, :parse_with_mock_date 164 | 165 | def mocked_time_stack_item 166 | Timecop.top_stack_item 167 | end 168 | end 169 | end 170 | 171 | module Process #:nodoc: 172 | class << self 173 | alias_method :clock_gettime_without_mock, :clock_gettime 174 | 175 | def clock_gettime_mock_time(clock_id, unit = :float_second) 176 | mock_time = case clock_id 177 | when Process::CLOCK_MONOTONIC 178 | mock_time_monotonic 179 | when Process::CLOCK_REALTIME 180 | mock_time_realtime 181 | end 182 | 183 | return clock_gettime_without_mock(clock_id, unit) unless Timecop.mock_process_clock? && mock_time 184 | 185 | divisor = case unit 186 | when :float_second 187 | 1_000_000_000.0 188 | when :second 189 | 1_000_000_000 190 | when :float_millisecond 191 | 1_000_000.0 192 | when :millisecond 193 | 1_000_000 194 | when :float_microsecond 195 | 1000.0 196 | when :microsecond 197 | 1000 198 | when :nanosecond 199 | 1 200 | end 201 | 202 | (mock_time / divisor) 203 | end 204 | 205 | alias_method :clock_gettime, :clock_gettime_mock_time 206 | 207 | private 208 | 209 | def mock_time_monotonic 210 | mocked_time_stack_item = Timecop.top_stack_item 211 | mocked_time_stack_item.nil? ? nil : mocked_time_stack_item.monotonic 212 | end 213 | 214 | def mock_time_realtime 215 | mocked_time_stack_item = Timecop.top_stack_item 216 | 217 | return nil if mocked_time_stack_item.nil? 218 | 219 | t = mocked_time_stack_item.time 220 | t.to_i * 1_000_000_000 + t.nsec 221 | end 222 | end 223 | end 224 | -------------------------------------------------------------------------------- /lib/timecop/time_stack_item.rb: -------------------------------------------------------------------------------- 1 | class Timecop 2 | # A data class for carrying around "time movement" objects. Makes it easy to keep track of the time 3 | # movements on a simple stack. 4 | class TimeStackItem #:nodoc: 5 | attr_reader :mock_type 6 | 7 | def initialize(mock_type, *args) 8 | raise "Unknown mock_type #{mock_type}" unless [:freeze, :travel, :scale].include?(mock_type) 9 | @travel_offset = @scaling_factor = nil 10 | @scaling_factor = args.shift if mock_type == :scale 11 | @mock_type = mock_type 12 | @monotonic = parse_monotonic_time(*args) 13 | @time = parse_time(*args) 14 | @time_was = Time.now_without_mock_time 15 | @travel_offset = compute_travel_offset 16 | end 17 | 18 | def year 19 | time.year 20 | end 21 | 22 | def month 23 | time.month 24 | end 25 | 26 | def day 27 | time.day 28 | end 29 | 30 | def hour 31 | time.hour 32 | end 33 | 34 | def min 35 | time.min 36 | end 37 | 38 | def sec 39 | time.sec 40 | end 41 | 42 | def utc_offset 43 | time.utc_offset 44 | end 45 | 46 | def travel_offset 47 | @travel_offset unless mock_type == :freeze 48 | end 49 | 50 | def travel_offset_days 51 | (@travel_offset / 60 / 60 / 24).round 52 | end 53 | 54 | def scaling_factor 55 | @scaling_factor 56 | end 57 | 58 | def monotonic 59 | if travel_offset.nil? 60 | @monotonic 61 | elsif scaling_factor.nil? 62 | current_monotonic + travel_offset * (10 ** 9) 63 | else 64 | (@monotonic + (current_monotonic - @monotonic) * scaling_factor).to_i 65 | end 66 | end 67 | 68 | def current_monotonic 69 | Process.clock_gettime_without_mock(Process::CLOCK_MONOTONIC, :nanosecond) 70 | end 71 | 72 | def current_monotonic_with_mock 73 | Process.clock_gettime_mock_time(Process::CLOCK_MONOTONIC, :nanosecond) 74 | end 75 | 76 | def time(time_klass = Time) #:nodoc: 77 | if @time.respond_to?(:in_time_zone) 78 | time = time_klass.at(@time.dup.localtime) 79 | else 80 | time = time_klass.at(@time) 81 | end 82 | 83 | if travel_offset.nil? 84 | time 85 | elsif scaling_factor.nil? 86 | time_klass.at(Time.now_without_mock_time + travel_offset) 87 | else 88 | time_klass.at(scaled_time) 89 | end 90 | end 91 | 92 | def scaled_time 93 | (@time + (Time.now_without_mock_time - @time_was) * scaling_factor).to_f 94 | end 95 | 96 | def date(date_klass = Date) 97 | date_klass.jd(time.__send__(:to_date).jd) 98 | end 99 | 100 | def datetime(datetime_klass = DateTime) 101 | if Float.method_defined?(:to_r) 102 | fractions_of_a_second = time.to_f % 1 103 | datetime_klass.new(year, month, day, hour, min, (fractions_of_a_second + sec), utc_offset_to_rational(utc_offset)) 104 | else 105 | datetime_klass.new(year, month, day, hour, min, sec, utc_offset_to_rational(utc_offset)) 106 | end 107 | end 108 | 109 | private 110 | 111 | def rational_to_utc_offset(rational) 112 | ((24.0 / rational.denominator) * rational.numerator) * (60 * 60) 113 | end 114 | 115 | def utc_offset_to_rational(utc_offset) 116 | Rational(utc_offset, 24 * 60 * 60) 117 | end 118 | 119 | def parse_monotonic_time(*args) 120 | arg = args.shift 121 | offset_in_nanoseconds = if args.empty? && (arg.kind_of?(Integer) || arg.kind_of?(Float)) 122 | arg * 1_000_000_000 123 | else 124 | 0 125 | end 126 | current_monotonic_with_mock + offset_in_nanoseconds 127 | end 128 | 129 | def parse_time(*args) 130 | arg = args.shift 131 | if arg.is_a?(Time) 132 | arg 133 | elsif Object.const_defined?(:DateTime) && arg.is_a?(DateTime) 134 | time_klass.at(arg.to_time.to_f).getlocal 135 | elsif Object.const_defined?(:Date) && arg.is_a?(Date) 136 | time_klass.local(arg.year, arg.month, arg.day, 0, 0, 0) 137 | elsif args.empty? && (arg.kind_of?(Integer) || arg.kind_of?(Float)) 138 | time_klass.now + arg 139 | elsif arg.nil? 140 | time_klass.now 141 | else 142 | if arg.is_a?(String) && Time.respond_to?(:parse) 143 | time_klass.parse(arg) 144 | else 145 | # we'll just assume it's a list of y/m/d/h/m/s 146 | year = arg || 2000 147 | month = args.shift || 1 148 | day = args.shift || 1 149 | hour = args.shift || 0 150 | minute = args.shift || 0 151 | second = args.shift || 0 152 | time_klass.local(year, month, day, hour, minute, second) 153 | end 154 | end 155 | end 156 | 157 | def compute_travel_offset 158 | time - Time.now_without_mock_time 159 | end 160 | 161 | def times_are_equal_within_epsilon t1, t2, epsilon_in_seconds 162 | (t1 - t2).abs < epsilon_in_seconds 163 | end 164 | 165 | def time_klass 166 | Time.respond_to?(:zone) && Time.zone ? Time.zone : Time 167 | end 168 | end 169 | end 170 | -------------------------------------------------------------------------------- /lib/timecop/timecop.rb: -------------------------------------------------------------------------------- 1 | require 'singleton' 2 | require File.join(File.dirname(__FILE__), "time_stack_item") 3 | 4 | # Timecop 5 | # * Wrapper class for manipulating the extensions to the Time, Date, and DateTime objects 6 | # * Allows us to "freeze" time in our Ruby applications. 7 | # * Optionally allows time travel to simulate a running clock, such time is not technically frozen. 8 | # 9 | # This is very useful when your app's functionality is dependent on time (e.g. 10 | # anything that might expire). This will allow us to alter the return value of 11 | # Date.today, Time.now, and DateTime.now, such that our application code _never_ has to change. 12 | class Timecop 13 | include Singleton 14 | 15 | class << self 16 | private :instance 17 | 18 | # Allows you to run a block of code and "fake" a time throughout the execution of that block. 19 | # This is particularly useful for writing test methods where the passage of time is critical to the business 20 | # logic being tested. For example: 21 | # 22 | # joe = User.find(1) 23 | # joe.purchase_home() 24 | # assert !joe.mortgage_due? 25 | # Timecop.freeze(2008, 10, 5) do 26 | # assert joe.mortgage_due? 27 | # end 28 | # 29 | # freeze and travel will respond to several different arguments: 30 | # 1. Timecop.freeze(time_inst) 31 | # 2. Timecop.freeze(datetime_inst) 32 | # 3. Timecop.freeze(date_inst) 33 | # 4. Timecop.freeze(offset_in_seconds) 34 | # 5. Timecop.freeze(year, month, day, hour=0, minute=0, second=0) 35 | # 6. Timecop.freeze() # Defaults to Time.now 36 | # 37 | # When a block is also passed, Time.now, DateTime.now and Date.today are all reset to their 38 | # previous values after the block has finished executing. This allows us to nest multiple 39 | # calls to Timecop.travel and have each block maintain it's concept of "now." 40 | # 41 | # The Process.clock_gettime call mocks both CLOCK::MONOTIC and CLOCK::REALTIME 42 | # 43 | # CLOCK::MONOTONIC works slightly differently than other clocks. This clock cannot move to a 44 | # particular date/time. So the only option that changes this clock is #4 which will move the 45 | # clock the requested offset. Otherwise the clock is frozen to the current tick. 46 | # 47 | # * Note: Timecop.freeze will actually freeze time. This can cause unanticipated problems if 48 | # benchmark or other timing calls are executed, which implicitly expect Time to actually move 49 | # forward. 50 | # 51 | # * Rails Users: Be especially careful when setting this in your development environment in a 52 | # rails project. Generators will load your environment, including the migration generator, 53 | # which will lead to files being generated with the timestamp set by the Timecop.freeze call 54 | # in your dev environment 55 | # 56 | # Returns the value of the block if one is given, or the mocked time. 57 | def freeze(*args, &block) 58 | send_travel(:freeze, *args, &block) 59 | end 60 | 61 | # Allows you to run a block of code and "fake" a time throughout the execution of that block. 62 | # See Timecop#freeze for a sample of how to use (same exact usage syntax) 63 | # 64 | # * Note: Timecop.travel will not freeze time (as opposed to Timecop.freeze). This is a particularly 65 | # good candidate for use in environment files in rails projects. 66 | # 67 | # Returns the value of the block if one is given, or the mocked time. 68 | def travel(*args, &block) 69 | send_travel(:travel, *args, &block) 70 | end 71 | 72 | # Allows you to run a block of code and "scale" a time throughout the execution of that block. 73 | # The first argument is a scaling factor, for example: 74 | # Timecop.scale(2) do 75 | # ... time will 'go' twice as fast here 76 | # end 77 | # See Timecop#freeze for exact usage of the other arguments 78 | # 79 | # Returns the value of the block if one is given, or the mocked time. 80 | def scale(*args, &block) 81 | send_travel(:scale, *args, &block) 82 | end 83 | 84 | def baseline 85 | instance.baseline 86 | end 87 | 88 | def baseline=(baseline) 89 | instance.baseline = baseline 90 | end 91 | 92 | # Reverts back to system's Time.now, Date.today and DateTime.now (if it exists) permamently when 93 | # no block argument is given, or temporarily reverts back to the system's time temporarily for 94 | # the given block. 95 | def return(&block) 96 | if block_given? 97 | instance.return(&block) 98 | else 99 | instance.unmock! 100 | nil 101 | end 102 | end 103 | alias :unfreeze :return 104 | 105 | def return_to_baseline 106 | instance.return_to_baseline 107 | Time.now 108 | end 109 | 110 | def top_stack_item #:nodoc: 111 | instance.stack.last 112 | end 113 | 114 | def safe_mode=(safe) 115 | @safe_mode = safe 116 | end 117 | 118 | def safe_mode? 119 | @safe_mode ||= false 120 | end 121 | 122 | def thread_safe=(t) 123 | instance.thread_safe = t 124 | end 125 | 126 | def thread_safe 127 | instance.thread_safe 128 | end 129 | 130 | # Returns whether or not Timecop is currently frozen 131 | def frozen? 132 | !instance.stack.empty? && instance.stack.last.mock_type == :freeze 133 | end 134 | 135 | # Returns whether or not Timecop is currently travelled 136 | def travelled? 137 | !instance.stack.empty? && instance.stack.last.mock_type == :travel 138 | end 139 | 140 | # Returns whether or not Timecop is currently scaled 141 | def scaled? 142 | !instance.stack.empty? && instance.stack.last.mock_type == :scale 143 | end 144 | 145 | def mock_process_clock=(mock) 146 | @mock_process_clock = mock 147 | end 148 | 149 | def mock_process_clock? 150 | @mock_process_clock ||= false 151 | end 152 | 153 | private 154 | def send_travel(mock_type, *args, &block) 155 | val = instance.travel(mock_type, *args, &block) 156 | block_given? ? val : Time.now 157 | end 158 | end 159 | 160 | def baseline=(b) 161 | set_baseline(b) 162 | stack << TimeStackItem.new(:travel, b) 163 | end 164 | 165 | def baseline 166 | if @thread_safe 167 | Thread.current[:timecop_baseline] 168 | else 169 | @baseline 170 | end 171 | end 172 | 173 | def set_baseline(b) 174 | if @thread_safe 175 | Thread.current[:timecop_baseline] = b 176 | else 177 | @baseline = b 178 | end 179 | end 180 | 181 | def stack 182 | if @thread_safe 183 | Thread.current[:timecop_stack] ||= [] 184 | Thread.current[:timecop_stack] 185 | else 186 | @stack 187 | end 188 | end 189 | 190 | def set_stack(s) 191 | if @thread_safe 192 | Thread.current[:timecop_stack] = s 193 | else 194 | @stack = s 195 | end 196 | end 197 | 198 | def initialize #:nodoc: 199 | @stack = [] 200 | @safe = nil 201 | @thread_safe = false 202 | end 203 | 204 | def thread_safe=(t) 205 | initialize 206 | @thread_safe = t 207 | end 208 | 209 | def thread_safe 210 | @thread_safe 211 | end 212 | 213 | def travel(mock_type, *args, &block) #:nodoc: 214 | raise SafeModeException if Timecop.safe_mode? && !block_given? && !@safe 215 | 216 | stack_item = TimeStackItem.new(mock_type, *args) 217 | 218 | stack_backup = stack.dup 219 | stack << stack_item 220 | 221 | if block_given? 222 | safe_backup = @safe 223 | @safe = true 224 | begin 225 | yield stack_item.time 226 | ensure 227 | stack.replace stack_backup 228 | @safe = safe_backup 229 | end 230 | end 231 | end 232 | 233 | def return(&block) 234 | current_stack = stack 235 | current_baseline = baseline 236 | unmock! 237 | yield 238 | ensure 239 | set_stack current_stack 240 | set_baseline current_baseline 241 | end 242 | 243 | def unmock! #:nodoc: 244 | set_baseline nil 245 | set_stack [] 246 | end 247 | 248 | def return_to_baseline 249 | if baseline 250 | set_stack [stack.shift] 251 | else 252 | unmock! 253 | end 254 | end 255 | 256 | class SafeModeException < StandardError 257 | def initialize 258 | super "Safe mode is enabled, only calls passing a block are allowed." 259 | end 260 | end 261 | end 262 | 263 | # This must be done after TimeCop is available 264 | require File.join(File.dirname(__FILE__), "time_extensions") 265 | -------------------------------------------------------------------------------- /lib/timecop/version.rb: -------------------------------------------------------------------------------- 1 | class Timecop 2 | VERSION = "0.9.10" 3 | end 4 | -------------------------------------------------------------------------------- /test/date_parse_scenarios.rb: -------------------------------------------------------------------------------- 1 | module DateParseScenarios 2 | 3 | def test_date_parse_sunday_after_travel 4 | assert_equal Date.parse("2008-08-31"), Date.parse('Sunday') 5 | assert_equal Date.parse("2008-08-31"), Date.parse('Sun') 6 | end 7 | 8 | def test_date_parse_monday_after_travel 9 | assert_equal Date.parse("2008-09-01"), Date.parse('Monday') 10 | assert_equal Date.parse("2008-09-01"), Date.parse('Mon') 11 | end 12 | 13 | def test_date_parse_tuesday_after_travel 14 | assert_equal Date.parse("2008-09-02"), Date.parse('Tuesday') 15 | assert_equal Date.parse("2008-09-02"), Date.parse('Tue') 16 | end 17 | 18 | def test_date_parse_wednesday_after_travel 19 | assert_equal Date.parse("2008-09-03"), Date.parse('Wednesday') 20 | assert_equal Date.parse("2008-09-03"), Date.parse('Wed') 21 | end 22 | 23 | def test_date_parse_thursday_after_travel 24 | assert_equal Date.parse("2008-09-04"), Date.parse('Thursday') 25 | assert_equal Date.parse("2008-09-04"), Date.parse('Thu') 26 | end 27 | 28 | def test_date_parse_friday_after_travel 29 | assert_equal Date.parse("2008-09-05"), Date.parse('Friday') 30 | assert_equal Date.parse("2008-09-05"), Date.parse('Fri') 31 | end 32 | 33 | def test_date_parse_saturday_after_travel 34 | assert_equal Date.parse("2008-09-06"), Date.parse('Saturday') 35 | assert_equal Date.parse("2008-09-06"), Date.parse('Sat') 36 | end 37 | 38 | def test_date_parse_with_additional_args 39 | assert_equal Date.parse("2008-09-06", false), Date.parse('Saturday') 40 | assert_equal Date.parse("2008-09-06", false), Date.parse('Sat') 41 | end 42 | 43 | def test_date_parse_10 44 | assert_equal Date.parse("2008-09-10"), Date.parse('10') 45 | end 46 | 47 | def test_date_parse_october_10 48 | assert_equal Date.parse("2008-10-10"), Date.parse('October 10') 49 | end 50 | 51 | def test_date_parse_1010 52 | assert_equal Date.parse("2008-10-10"), Date.parse('1010') 53 | end 54 | 55 | def test_date_parse_10_slash_10 56 | assert_equal Date.parse("2008-10-10"), Date.parse('10/10') 57 | end 58 | 59 | def test_date_parse_Date_10_slash_10 60 | assert_equal Date.parse("2008-10-10"), Date.parse('Date 10/10') 61 | end 62 | 63 | def test_date_parse_month_year 64 | assert_equal Date.parse("2012-12-01"), Date.parse('DEC 2012') 65 | end 66 | 67 | def test_date_parse_nil_raises_type_error 68 | assert_raises(TypeError) { Date.parse(nil) } 69 | end 70 | 71 | def test_date_parse_non_string_raises_expected_error 72 | assert_raises(TypeError) { Date.parse(Object.new) } 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /test/date_strptime_scenarios.rb: -------------------------------------------------------------------------------- 1 | module DateStrptimeScenarios 2 | 3 | #calling freeze and travel tests are making the date Time.local(1984,2,28) 4 | 5 | def test_date_strptime_with_year 6 | assert_equal Date.strptime('1999', '%Y'), Date.new(1999, 1, 1) 7 | end 8 | 9 | def test_date_strptime_without_year 10 | assert_equal Date.strptime('04-14', '%m-%d'), Date.new(1984, 4, 14) 11 | end 12 | 13 | def test_date_strptime_without_day 14 | assert_equal Date.strptime('1999-04', '%Y-%m'), Date.new(1999, 4, 1) 15 | end 16 | 17 | def test_date_strptime_day_of_year_without_year 18 | assert_equal Date.strptime("153", '%j'), Date.new(1984, 6, 1) 19 | end 20 | 21 | def test_date_strptime_day_of_year_with_year 22 | assert_equal Date.strptime("1999 153", '%Y %j'), Date.new(1999, 6, 2) 23 | end 24 | 25 | 26 | def test_date_strptime_without_specifying_format 27 | assert_equal Date.strptime('1999-04-14'), Date.new(1999, 4, 14) 28 | end 29 | 30 | def test_date_strptime_with_day_of_week 31 | assert_equal Date.strptime('Thursday', '%A'), Date.new(1984, 3, 1) 32 | assert_equal Date.strptime('Monday', '%A'), Date.new(1984, 2, 27) 33 | end 34 | 35 | def test_date_strptime_with_commercial_week_date 36 | assert_equal Date.strptime('1984-09', '%G-%V'), Date.new(1984, 2, 27) 37 | end 38 | 39 | def test_date_strptime_with_commercial_week_date_and_day_of_week_from_sunday 40 | #2/27/1984 is a monday. wed the 29th is the last day of march. 41 | 42 | #1984-09 is 9th commercial week of 1984 starting on monday 2/27 43 | #specifying day of week = 0 with non-commercial day of week means 44 | #we jump to sunday, so 6 days after monday 2/27 which is 3/4 45 | assert_equal Date.strptime('1984-09-0', '%G-%V-%w'), Date.new(1984, 3, 04) 46 | assert_equal Date.strptime('1984-09-1', '%G-%V-%w'), Date.new(1984, 2, 27) 47 | assert_equal Date.strptime('1984-09-2', '%G-%V-%w'), Date.new(1984, 2, 28) 48 | assert_equal Date.strptime('1984-09-3', '%G-%V-%w'), Date.new(1984, 2, 29) 49 | assert_equal Date.strptime('1984-09-6', '%G-%V-%w'), Date.new(1984, 3, 03) 50 | 51 | #1984-09 is 9th commercial week of 1984 starting on a monday 52 | #specifying day of week = 1 with commercial day of week means stay at the 27th 53 | assert_equal Date.strptime('1984-09-1', '%G-%V-%u'), Date.new(1984, 2, 27) 54 | assert_equal Date.strptime('1984-09-2', '%G-%V-%u'), Date.new(1984, 2, 28) 55 | assert_equal Date.strptime('1984-09-3', '%G-%V-%u'), Date.new(1984, 2, 29) 56 | assert_equal Date.strptime('1984-09-7', '%G-%V-%u'), Date.new(1984, 3, 04) 57 | end 58 | 59 | def test_date_strptime_week_number_of_year_day_of_week_sunday_start 60 | assert_equal Date.strptime('1984 09 0', '%Y %U %w'), Date.new(1984, 2, 26) 61 | end 62 | 63 | def test_date_strptime_with_iso_8601_week_date 64 | assert_equal Date.strptime('1984-W09-1', '%G-W%V-%u'), Date.new(1984, 2, 27) 65 | end 66 | 67 | def test_date_strptime_with_year_and_week_number_of_year 68 | assert_equal Date.strptime('201810', '%Y%W'), Date.new(2018, 3, 5) 69 | end 70 | 71 | def test_date_strptime_with_year_and_week_number_of_year_and_day_of_week_from_monday 72 | assert_equal Date.strptime('2018107', '%Y%W%u'), Date.new(2018, 3, 11) 73 | end 74 | 75 | def test_date_strptime_with_just_week_number_of_year 76 | assert_equal Date.strptime('14', '%W'), Date.new(1984, 4, 02) 77 | end 78 | 79 | def test_date_strptime_week_of_year_and_day_of_week_from_sunday 80 | assert_equal Date.strptime('140', '%W%w'), Date.new(1984, 4, 8) 81 | end 82 | 83 | def test_date_strptime_with_seconds_since_epoch 84 | assert_equal Date.strptime('446731200', '%s'), Date.new(1984, 2, 27) 85 | end 86 | 87 | def test_date_strptime_with_miliseconds_since_epoch 88 | assert_equal Date.strptime('446731200000', '%Q'), Date.new(1984, 2, 27) 89 | end 90 | 91 | def test_date_strptime_with_invalid_date 92 | assert_raises(ArgumentError) { Date.strptime('', '%Y-%m-%d') } 93 | end 94 | 95 | def test_date_strptime_with_gregorian 96 | assert_equal Date.strptime('1999-04-01', '%Y-%m-%d', Date::GREGORIAN), Date.new(1999, 4, 1) 97 | end 98 | 99 | def test_date_strptime_with_gregorian_non_leap 100 | assert(!Date.strptime('1000-04-01', '%Y-%m-%d', Date::GREGORIAN).leap?) 101 | end 102 | 103 | def test_date_strptime_with_julian_leap 104 | assert(Date.strptime('1000-04-01', '%Y-%m-%d', Date::JULIAN).leap?) 105 | end 106 | 107 | def test_ancient_strptime 108 | ancient = Date.strptime('11-01-08', '%Y-%m-%d').strftime 109 | assert_equal '0011-01-08', ancient # Failed before fix to strptime_with_mock_date 110 | end 111 | 112 | def test_strptime_defaults_correctly 113 | assert_equal(Date.new, Date.strptime) 114 | end 115 | 116 | def test_strptime_from_date_to_s 117 | d = Date.new(1984, 3, 1) 118 | assert_equal(d, Date.strptime(d.to_s)) 119 | end 120 | 121 | def test_strptime_converts_back_and_forth_between_date_and_string_for_many_formats_every_day_of_the_year 122 | (Date.new(2006,6,1)..Date.new(2007,6,1)).each do |d| 123 | [ 124 | '%Y %m %d', 125 | '%C %y %m %d', 126 | '%Y %j', 127 | '%C %y %j', 128 | '%G %V %w', 129 | '%G %V %u', 130 | '%C %g %V %w', 131 | '%C %g %V %u', 132 | '%Y %W %w', 133 | '%Y %W %u', 134 | '%C %y %W %w', 135 | '%C %y %W %u', 136 | '%Y %U %w', 137 | '%Y %U %u', 138 | '%C %y %U %w', 139 | '%C %y %U %u', 140 | ].each do |fmt| 141 | s = d.strftime(fmt) 142 | d2 = Date.strptime(s, fmt) 143 | assert_equal(d, d2, [fmt, d.to_s, d2.to_s].inspect) 144 | end 145 | 146 | end 147 | end 148 | 149 | def test_strptime_raises_when_unparsable 150 | assert_raises(ArgumentError) do 151 | Date.strptime('') 152 | end 153 | assert_raises(ArgumentError) do 154 | Date.strptime('2001-02-29', '%F') 155 | end 156 | assert_raises(ArgumentError) do 157 | Date.strptime('01-31-2011', '%m/%d/%Y') 158 | end 159 | end 160 | 161 | def test_strptime_of_time_string_raises 162 | #TODO: this is a bug 163 | skip("TODO: broken contract") 164 | assert_raises(ArgumentError) do 165 | Date.strptime('23:55', '%H:%M') 166 | end 167 | end 168 | 169 | end 170 | -------------------------------------------------------------------------------- /test/date_time_parse_scenarios.rb: -------------------------------------------------------------------------------- 1 | module DateTimeParseScenarios 2 | 3 | def test_date_time_parse_sunday_after_travel 4 | assert_equal DateTime.parse("2008-08-31"), DateTime.parse('Sunday') 5 | assert_equal DateTime.parse("2008-08-31"), DateTime.parse('Sun') 6 | end 7 | 8 | def test_date_time_parse_monday_after_travel 9 | assert_equal DateTime.parse("2008-09-01"), DateTime.parse('Monday') 10 | assert_equal DateTime.parse("2008-09-01"), DateTime.parse('Mon') 11 | end 12 | 13 | def test_date_time_parse_tuesday_after_travel 14 | assert_equal DateTime.parse("2008-09-02"), DateTime.parse('Tuesday') 15 | assert_equal DateTime.parse("2008-09-02"), DateTime.parse('Tue') 16 | end 17 | 18 | def test_date_time_parse_wednesday_after_travel 19 | assert_equal DateTime.parse("2008-09-03"), DateTime.parse('Wednesday') 20 | assert_equal DateTime.parse("2008-09-03"), DateTime.parse('Wed') 21 | end 22 | 23 | def test_date_time_parse_thursday_after_travel 24 | assert_equal DateTime.parse("2008-09-04"), DateTime.parse('Thursday') 25 | assert_equal DateTime.parse("2008-09-04"), DateTime.parse('Thu') 26 | end 27 | 28 | def test_date_time_parse_friday_after_travel 29 | assert_equal DateTime.parse("2008-09-05"), DateTime.parse('Friday') 30 | assert_equal DateTime.parse("2008-09-05"), DateTime.parse('Fri') 31 | end 32 | 33 | def test_date_time_parse_saturday_after_travel 34 | assert_equal DateTime.parse("2008-09-06"), DateTime.parse('Saturday') 35 | assert_equal DateTime.parse("2008-09-06"), DateTime.parse('Sat') 36 | end 37 | 38 | def test_date_time_parse_with_additional_args 39 | assert_equal DateTime.parse("2008-09-06", false), DateTime.parse('Saturday') 40 | assert_equal DateTime.parse("2008-09-06", false), DateTime.parse('Sat') 41 | end 42 | 43 | def test_date_time_parse_10 44 | assert_equal DateTime.parse("2008-09-10"), DateTime.parse('10') 45 | end 46 | 47 | def test_date_time_parse_october_10 48 | assert_equal DateTime.parse("2008-10-10"), DateTime.parse('October 10') 49 | end 50 | 51 | def test_date_time_parse_1010 52 | assert_equal DateTime.parse("2008-10-10"), DateTime.parse('1010') 53 | end 54 | 55 | def test_date_time_parse_10_slash_10 56 | assert_equal DateTime.parse("2008-10-10"), DateTime.parse('10/10') 57 | end 58 | 59 | def test_date_time_parse_Date_10_slash_10 60 | assert_equal DateTime.parse("2008-10-10"), DateTime.parse('Date 10/10') 61 | end 62 | 63 | def test_date_time_parse_time_only_scenario 64 | assert_equal DateTime.parse("2008-09-01T15:00:00"), DateTime.parse('15:00:00') 65 | end 66 | 67 | def test_date_time_parse_month_year 68 | assert_equal DateTime.parse("2012-12-01"), DateTime.parse('DEC 2012') 69 | end 70 | 71 | def test_date_time_parse_wday_with_hour 72 | assert_equal DateTime.parse("2008-09-06T13:00:00"), DateTime.parse('Saturday 13:00') 73 | end 74 | 75 | def test_date_time_parse_non_string_raises_expected_error 76 | assert_raises(TypeError) { DateTime.parse(Object.new) } 77 | end 78 | 79 | def test_datetime_parse_nil_raises_type_error 80 | assert_raises(TypeError) { DateTime.parse(nil) } 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'minitest/autorun' 3 | require 'pry' 4 | 5 | $VERBOSE = true # enable ruby warnings 6 | 7 | require 'mocha/minitest' 8 | 9 | class Minitest::Test 10 | private 11 | # Tests to see that two times are within the given distance, 12 | # in seconds, from each other. 13 | def times_effectively_equal(time1, time2, seconds_interval = 1) 14 | (time1 - time2).abs <= seconds_interval 15 | end 16 | 17 | def assert_times_effectively_equal(time1, time2, seconds_interval = 1, msg = nil) 18 | assert times_effectively_equal(time1, time2, seconds_interval), "#{msg}: time1 = #{time1.to_s}, time2 = #{time2.to_s}" 19 | end 20 | 21 | def assert_times_effectively_not_equal(time1, time2, seconds_interval = 1, msg = nil) 22 | assert !times_effectively_equal(time1, time2, seconds_interval), "#{msg}: time1 = #{time1.to_s}, time2 = #{time2.to_s}" 23 | end 24 | 25 | # Gets the local offset (supplied by ENV['TZ'] or your computer's clock) 26 | # At the given timestamp, or Time.now if not time is given. 27 | def local_offset(time = Time.now) 28 | Time.at(time.to_i).to_datetime.offset 29 | end 30 | 31 | TIMEZONES = ["Pacific/Midway", "Europe/Paris", "UTC", "America/Chicago"] 32 | 33 | def each_timezone 34 | old_tz = ENV["TZ"] 35 | 36 | begin 37 | TIMEZONES.each do |timezone| 38 | ENV["TZ"] = timezone 39 | yield 40 | end 41 | ensure 42 | ENV["TZ"] = old_tz 43 | end 44 | end 45 | 46 | def a_time_stack_item 47 | Timecop::TimeStackItem.new(:freeze, 2008, 1, 1, 0, 0, 0) 48 | end 49 | 50 | def assert_date_times_equal(dt1, dt2) 51 | assert_in_delta dt1.to_time.to_f, dt2.to_time.to_f, 0.01, "Failed for timezone: #{ENV['TZ']}: #{dt1.to_s} not equal to #{dt2.to_s}" 52 | end 53 | 54 | def jruby? 55 | RUBY_PLATFORM == "java" 56 | end 57 | 58 | end 59 | -------------------------------------------------------------------------------- /test/time_stack_item_test.rb: -------------------------------------------------------------------------------- 1 | require 'date' 2 | require_relative "test_helper" 3 | require 'timecop' 4 | 5 | require 'active_support/all' 6 | 7 | class TestTimeStackItem < Minitest::Test 8 | def teardown 9 | Timecop.return 10 | Time.zone = nil 11 | end 12 | 13 | def test_new_with_time 14 | t = Time.now 15 | y, m, d, h, min, s = t.year, t.month, t.day, t.hour, t.min, t.sec 16 | stack_item = Timecop::TimeStackItem.new(:freeze, t) 17 | 18 | assert_equal y, stack_item.year 19 | assert_equal m, stack_item.month 20 | assert_equal d, stack_item.day 21 | assert_equal h, stack_item.hour 22 | assert_equal min, stack_item.min 23 | assert_equal s, stack_item.sec 24 | end 25 | 26 | def test_new_with_time_and_arguments 27 | t = Time.new(2012, 7, 28, 20, 0) 28 | y, m, d, h, min, s = t.year, t.month, t.day, t.hour, t.min, t.sec 29 | stack_item = Timecop::TimeStackItem.new(:freeze, t) 30 | 31 | assert_equal y, stack_item.year 32 | assert_equal m, stack_item.month 33 | assert_equal d, stack_item.day 34 | assert_equal h, stack_item.hour 35 | assert_equal min, stack_item.min 36 | assert_equal s, stack_item.sec 37 | end 38 | 39 | def test_new_with_datetime_now 40 | t = DateTime.now 41 | y, m, d, h, min, s = t.year, t.month, t.day, t.hour, t.min, t.sec 42 | stack_item = Timecop::TimeStackItem.new(:freeze, t) 43 | 44 | assert_equal y, stack_item.year 45 | assert_equal m, stack_item.month 46 | assert_equal d, stack_item.day 47 | assert_equal h, stack_item.hour 48 | assert_equal min, stack_item.min 49 | assert_equal s, stack_item.sec 50 | end 51 | 52 | def test_new_with_datetime_in_different_timezone 53 | each_timezone do 54 | t = DateTime.parse("2009-10-11 00:38:00 +0200") 55 | stack_item = Timecop::TimeStackItem.new(:freeze, t) 56 | 57 | assert_date_times_equal(t, stack_item.datetime) 58 | end 59 | end 60 | 61 | def test_new_with_date 62 | date = Date.today 63 | y, m, d, h, min, s = date.year, date.month, date.day, 0, 0, 0 64 | stack_item = Timecop::TimeStackItem.new(:freeze, date) 65 | 66 | assert_equal y, stack_item.year 67 | assert_equal m, stack_item.month 68 | assert_equal d, stack_item.day 69 | assert_equal h, stack_item.hour 70 | assert_equal min, stack_item.min 71 | assert_equal s, stack_item.sec 72 | end 73 | 74 | # Due to the nature of this test (calling Time.now once in this test and 75 | # once in #new), this test may fail when two subsequent calls 76 | # to Time.now return a different second. 77 | def test_new_with_integer 78 | t = Time.now 79 | y, m, d, h, min, s = t.year, t.month, t.day, t.hour, t.min, t.sec 80 | stack_item = Timecop::TimeStackItem.new(:freeze, 0) 81 | 82 | assert_equal y, stack_item.year 83 | assert_equal m, stack_item.month 84 | assert_equal d, stack_item.day 85 | assert_equal h, stack_item.hour 86 | assert_equal min, stack_item.min 87 | assert_equal s, stack_item.sec 88 | end 89 | 90 | def test_new_with_float 91 | t = Time.now 92 | y, m, d, h, min, s = t.year, t.month, t.day, t.hour, t.min, t.sec 93 | stack_item = Timecop::TimeStackItem.new(:freeze, 0.0) 94 | 95 | assert_equal y, stack_item.year 96 | assert_equal m, stack_item.month 97 | assert_equal d, stack_item.day 98 | assert_equal h, stack_item.hour 99 | assert_equal min, stack_item.min 100 | assert_equal s, stack_item.sec 101 | end 102 | 103 | def test_new_with_individual_arguments 104 | y, m, d, h, min, s = 2008, 10, 10, 10, 10, 10 105 | stack_item = Timecop::TimeStackItem.new(:freeze, y, m, d, h, min, s) 106 | 107 | assert_equal y, stack_item.year 108 | assert_equal m, stack_item.month 109 | assert_equal d, stack_item.day 110 | assert_equal h, stack_item.hour 111 | assert_equal min, stack_item.min 112 | assert_equal s, stack_item.sec 113 | end 114 | 115 | def test_rational_to_utc_offset 116 | assert_equal(-14400, a_time_stack_item.send(:rational_to_utc_offset, Rational(-1, 6))) 117 | assert_equal(-18000, a_time_stack_item.send(:rational_to_utc_offset, Rational(-5, 24))) 118 | assert_equal 0, a_time_stack_item.send(:rational_to_utc_offset, Rational(0, 1)) 119 | assert_equal 3600, a_time_stack_item.send(:rational_to_utc_offset, Rational(1, 24)) 120 | end 121 | 122 | def test_utc_offset_to_rational 123 | assert_equal Rational(-1, 6), a_time_stack_item.send(:utc_offset_to_rational, -14400) 124 | assert_equal Rational(-5, 24), a_time_stack_item.send(:utc_offset_to_rational, -18000) 125 | assert_equal Rational(0, 1), a_time_stack_item.send(:utc_offset_to_rational, 0) 126 | assert_equal Rational(1, 24), a_time_stack_item.send(:utc_offset_to_rational, 3600) 127 | end 128 | 129 | def test_datetime_in_presence_of_activesupport_timezone 130 | skip('requires ActiveSupport') unless Time.respond_to? :zone 131 | backed_up_zone, backed_up_tzvar = Time.zone, ENV['TZ'] 132 | 133 | Time.zone = ENV['TZ'] = 'America/Los_Angeles' 134 | t = DateTime.new(2001, 2, 28, 23, 59, 59.5) 135 | tsi = Timecop::TimeStackItem.new(:freeze, t) 136 | 137 | assert_date_times_equal t, tsi.datetime 138 | ensure 139 | Time.zone, ENV['TZ'] = backed_up_zone, backed_up_tzvar 140 | end 141 | 142 | # Ensure DateTimes handle changing DST properly 143 | def test_datetime_for_dst_to_non_dst 144 | Timecop.freeze(DateTime.parse("2009-12-1 00:38:00 -0500")) 145 | t = DateTime.parse("2009-10-11 00:00:00 -0400") 146 | tsi = Timecop::TimeStackItem.new(:freeze, t) 147 | 148 | assert_date_times_equal t, tsi.datetime 149 | end 150 | 151 | # Ensure DateTimes handle changing DST properly when changing from DateTime to Time 152 | def test_datetime_for_dst_to_time_for_non_dst 153 | Timecop.freeze(DateTime.parse("2009-12-1 00:38:00 -0500")) 154 | t = DateTime.parse("2009-10-11 00:00:00 -0400") 155 | tsi = Timecop::TimeStackItem.new(:freeze, t) 156 | 157 | assert_date_times_equal t.to_time, tsi.time 158 | end 159 | 160 | def test_datetime_for_non_dst_to_dst 161 | Timecop.freeze(DateTime.parse("2009-10-11 00:00:00 -0400")) 162 | t = DateTime.parse("2009-11-30 23:38:00 -0500") 163 | tsi = Timecop::TimeStackItem.new(:freeze, t) 164 | return if !tsi.time.dst? 165 | 166 | assert_date_times_equal t, tsi.datetime 167 | assert_equal Date.new(2009, 12, 1), tsi.date 168 | end 169 | 170 | def test_set_travel_offset_for_travel 171 | t_now = Time.now 172 | t = Time.local(2009, 10, 1, 0, 0, 30) 173 | expected_offset = t - t_now 174 | tsi = Timecop::TimeStackItem.new(:travel, t) 175 | 176 | assert_times_effectively_equal expected_offset, tsi.send(:travel_offset), 1, "Offset not calculated correctly" 177 | end 178 | 179 | def test_set_travel_offset_for_freeze 180 | Timecop.freeze(2009, 10, 1, 0, 0, 0) 181 | t = Time.local(2009, 10, 1, 0, 0, 30) 182 | tsi = Timecop::TimeStackItem.new(:freeze, t) 183 | 184 | assert_nil tsi.send(:travel_offset) 185 | end 186 | 187 | def test_timezones 188 | Time.zone = "Europe/Zurich" 189 | time = Time.zone.parse("2012-12-27T12:12:12+08:00") 190 | Timecop.freeze(time) do |frozen_time| 191 | assert_equal time, frozen_time 192 | end 193 | end 194 | 195 | def test_timezones_with_parsed_string 196 | Time.zone = "Europe/Zurich" 197 | time_string = "2012-12-27 12:12" 198 | expected_time = Time.zone.parse(time_string) 199 | Timecop.freeze(time_string) do |frozen_time| 200 | assert_equal expected_time, frozen_time 201 | end 202 | end 203 | 204 | def test_timezones_apply_dates 205 | Time.zone = "Central Time (US & Canada)" 206 | time = Time.zone.local(2013,1,3) 207 | 208 | Timecop.freeze(time) do 209 | assert_equal time.to_date, Time.zone.now.to_date 210 | end 211 | end 212 | 213 | def test_set_scaling_factor_for_scale 214 | t_now = Time.now 215 | t = Time.local(2009, 10, 1, 0, 0, 30) 216 | expected_offset = t - t_now 217 | tsi = Timecop::TimeStackItem.new(:scale, 4, t) 218 | 219 | assert_times_effectively_equal expected_offset, tsi.send(:travel_offset), 1, "Offset not calculated correctly" 220 | assert_equal tsi.send(:scaling_factor), 4, "Scaling factor not set" 221 | end 222 | 223 | def test_parse_only_string_with_active_support 224 | Time.expects(:parse).never 225 | Timecop.freeze(2011, 1, 2, 0, 0, 0) 226 | end 227 | 228 | def test_parse_date 229 | Timecop.freeze(Date.new(2012, 6, 9)) 230 | end 231 | 232 | def test_time_zone_returns_nil 233 | Time.zone = nil 234 | Timecop.freeze 235 | end 236 | 237 | def test_nsecs_are_set 238 | time = Time.now 239 | Timecop.freeze time 240 | assert_equal time, Time.now 241 | assert_equal time.nsec, Time.now.nsec if (Time.now.respond_to?(:nsec)) 242 | end 243 | 244 | def test_time_with_different_timezone_keeps_nsec 245 | Time.zone = "Tokyo" 246 | t = Time.now 247 | Timecop.freeze(t) do 248 | assert_equal t, Time.now 249 | assert_equal t.nsec, Time.now.nsec if (Time.now.respond_to?(:nsec)) 250 | end 251 | end 252 | 253 | def test_time_now_always_returns_local_time 254 | Time.zone = "Tokyo" 255 | t = Time.utc(2000, 1, 1) 256 | Timecop.freeze(t) do 257 | assert_equal t.getlocal.zone, Time.now.zone 258 | end 259 | end 260 | 261 | def test_time_zone_now_returns_time_in_that_zone 262 | Time.zone = "Hawaii" 263 | t = Time.utc(2000, 1, 1) 264 | Timecop.freeze(t) do 265 | assert_equal t, Time.zone.now 266 | assert_equal 'HST', Time.zone.now.zone 267 | end 268 | end 269 | 270 | def test_freezing_a_time_leaves_timezone_intact 271 | Time.zone = "Tokyo" 272 | t = Time.now 273 | t_dup = t.dup 274 | Timecop.freeze(t) {} 275 | assert_equal t_dup.zone, t.zone 276 | end 277 | 278 | def test_freezing_a_time_with_zone_returns_proper_zones 279 | Time.zone = "Hawaii" 280 | t = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone['Tokyo']) 281 | Timecop.freeze(t) do 282 | local_now = Time.now 283 | assert_equal t, local_now 284 | assert_equal t.getlocal.zone, local_now.zone 285 | 286 | zoned_now = Time.zone.now 287 | assert_equal t, zoned_now 288 | assert_equal 'HST', zoned_now.zone 289 | end 290 | end 291 | 292 | def test_datetime_timezones 293 | dt = DateTime.new(2011,1,3,15,25,0,"-6") 294 | Timecop.freeze(dt) do 295 | now = DateTime.now 296 | assert_equal dt, now, "#{dt.to_f}, #{now.to_f}" 297 | end 298 | end 299 | end 300 | -------------------------------------------------------------------------------- /test/timecop_date_parse_freeze_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | require 'timecop' 3 | require_relative 'date_parse_scenarios' 4 | require_relative 'date_time_parse_scenarios' 5 | 6 | class TestTimecop < Minitest::Test 7 | def setup 8 | t = Time.local(2008, 9, 1, 10, 5, 0) # monday 9 | Timecop.freeze(t) 10 | end 11 | 12 | def teardown 13 | Timecop.return 14 | end 15 | 16 | # Test for Date 17 | include DateParseScenarios 18 | # Tests for DateTime 19 | include DateTimeParseScenarios 20 | end 21 | -------------------------------------------------------------------------------- /test/timecop_date_parse_travel_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | require 'timecop' 3 | require_relative 'date_parse_scenarios' 4 | require_relative 'date_time_parse_scenarios' 5 | 6 | class TestTimecop < Minitest::Test 7 | def setup 8 | t = Time.local(2008, 9, 1, 10, 5, 0) # monday 9 | Timecop.travel(t) 10 | end 11 | 12 | def teardown 13 | Timecop.return 14 | end 15 | 16 | # Test for Date 17 | include DateParseScenarios 18 | 19 | # Tests for DateTime 20 | include DateTimeParseScenarios 21 | end 22 | -------------------------------------------------------------------------------- /test/timecop_date_strptime_freeze_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | require 'timecop' 3 | require_relative 'date_strptime_scenarios' 4 | 5 | class TestTimecop < Minitest::Test 6 | def setup 7 | t = Time.local(1984,2,28) 8 | Timecop.freeze(t) 9 | end 10 | 11 | def teardown 12 | Timecop.return 13 | end 14 | 15 | # Test for Date 16 | include DateStrptimeScenarios 17 | 18 | end 19 | -------------------------------------------------------------------------------- /test/timecop_date_strptime_travel_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | require 'timecop' 3 | require_relative 'date_strptime_scenarios' 4 | 5 | class TestTimecop < Minitest::Test 6 | def setup 7 | t = Time.local(1984,2,28) 8 | Timecop.travel(t) 9 | end 10 | 11 | def teardown 12 | Timecop.return 13 | end 14 | 15 | # Test for Date 16 | include DateStrptimeScenarios 17 | 18 | end 19 | -------------------------------------------------------------------------------- /test/timecop_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | require 'timecop' 3 | 4 | class TestTimecop < Minitest::Test 5 | def teardown 6 | Timecop.return 7 | end 8 | 9 | def test_freeze_changes_and_resets_time 10 | outer_freeze_time = Time.local(2001, 01, 01) 11 | inner_freeze_block = Time.local(2002, 02, 02) 12 | inner_freeze_one = Time.local(2003, 03, 03) 13 | inner_freeze_two = Time.local(2004, 04, 04) 14 | 15 | Timecop.freeze(outer_freeze_time) do 16 | assert_times_effectively_equal outer_freeze_time, Time.now 17 | Timecop.freeze(inner_freeze_block) do 18 | assert_times_effectively_equal inner_freeze_block, Time.now 19 | Timecop.freeze(inner_freeze_one) 20 | assert_times_effectively_equal inner_freeze_one, Time.now 21 | Timecop.freeze(inner_freeze_two) 22 | assert_times_effectively_equal inner_freeze_two, Time.now 23 | end 24 | assert_times_effectively_equal outer_freeze_time, Time.now 25 | end 26 | end 27 | 28 | def test_freeze_yields_mocked_time 29 | Timecop.freeze(2008, 10, 10, 10, 10, 10) do |frozen_time| 30 | assert_equal frozen_time, Time.now 31 | end 32 | end 33 | 34 | def test_freeze_then_return_unsets_mock_time 35 | Timecop.freeze(1) 36 | Timecop.return 37 | assert_nil Time.send(:mock_time) 38 | end 39 | 40 | def test_freeze_then_unfreeze_unsets_mock_time 41 | Timecop.freeze(1) 42 | Timecop.unfreeze 43 | assert_nil Time.send(:mock_time) 44 | end 45 | 46 | def test_travel_then_return_unsets_mock_time 47 | Timecop.travel(1) 48 | Timecop.return 49 | assert_nil Time.send(:mock_time) 50 | end 51 | 52 | def test_freeze_with_block_unsets_mock_time 53 | assert_nil Time.send(:mock_time), "test is invalid" 54 | Timecop.freeze(1) do; end 55 | assert_nil Time.send(:mock_time) 56 | end 57 | 58 | def test_travel_with_block_unsets_mock_time 59 | assert_nil Time.send(:mock_time), "test is invalid" 60 | Timecop.travel(1) do; end 61 | assert_nil Time.send(:mock_time) 62 | end 63 | 64 | def test_travel_does_not_reduce_precision_of_datetime 65 | # requires to_r on Float (>= 1.9) 66 | if Float.method_defined?(:to_r) 67 | Timecop.travel(Time.new(2014, 1, 1, 0, 0, 0)) 68 | assert DateTime.now != DateTime.now 69 | 70 | Timecop.travel(Time.new(2014, 1, 1, 0, 0, 59)) 71 | assert DateTime.now != DateTime.now 72 | end 73 | end 74 | 75 | def test_freeze_in_time_subclass_returns_mocked_subclass 76 | custom_timeklass = Class.new(Time) do 77 | def custom_format_method() strftime('%F') end 78 | end 79 | 80 | Timecop.freeze(2008, 10, 10, 10, 10, 10) do 81 | assert custom_timeklass.now.is_a? custom_timeklass 82 | assert Time.now.eql? custom_timeklass.now 83 | assert custom_timeklass.now.respond_to? :custom_format_method 84 | end 85 | end 86 | 87 | def test_freeze_in_date_subclass_returns_mocked_subclass 88 | custom_dateklass = Class.new(Date) do 89 | def custom_format_method() strftime('%F') end 90 | end 91 | 92 | Timecop.freeze(2008, 10, 10, 10, 10, 10) do 93 | assert custom_dateklass.today.is_a? custom_dateklass 94 | assert Date.today.eql? custom_dateklass.today 95 | assert custom_dateklass.today.respond_to? :custom_format_method 96 | end 97 | end 98 | 99 | def test_freeze_in_datetime_subclass_returns_mocked_subclass 100 | custom_datetimeklass = Class.new(DateTime) do 101 | def custom_format_method() strftime('%F') end 102 | end 103 | 104 | Timecop.freeze(2008, 10, 10, 10, 10, 10) do 105 | assert custom_datetimeklass.now.is_a? custom_datetimeklass 106 | assert DateTime.now.eql? custom_datetimeklass.now 107 | assert custom_datetimeklass.now.respond_to? :custom_format_method 108 | end 109 | end 110 | 111 | def test_recursive_freeze 112 | t = Time.local(2008, 10, 10, 10, 10, 10) 113 | Timecop.freeze(2008, 10, 10, 10, 10, 10) do 114 | assert_equal t, Time.now 115 | t2 = Time.local(2008, 9, 9, 9, 9, 9) 116 | Timecop.freeze(2008, 9, 9, 9, 9, 9) do 117 | assert_equal t2, Time.now 118 | end 119 | assert_equal t, Time.now 120 | end 121 | assert t != Time.now 122 | end 123 | 124 | def test_freeze_with_time_instance_works_as_expected 125 | t = Time.local(2008, 10, 10, 10, 10, 10) 126 | Timecop.freeze(t) do 127 | assert_equal t, Time.now 128 | assert_date_times_equal DateTime.new(2008, 10, 10, 10, 10, 10, local_offset), DateTime.now 129 | assert_equal Date.new(2008, 10, 10), Date.today 130 | end 131 | 132 | assert t != Time.now 133 | assert DateTime.new(2008, 10, 10, 10, 10, 10, local_offset) != DateTime.now 134 | assert Date.new(2008, 10, 10) != Date.today 135 | end 136 | 137 | def test_freeze_with_datetime_on_specific_timezone_during_dst 138 | each_timezone do 139 | # Start from a time that is subject to DST 140 | Timecop.freeze(2009, 9, 1) 141 | # Travel to a DateTime that is also in DST 142 | t = DateTime.parse("2009-10-11 00:38:00 +0200") 143 | Timecop.freeze(t) do 144 | assert_date_times_equal t, DateTime.now 145 | end 146 | Timecop.return 147 | end 148 | end 149 | 150 | def test_freeze_with_datetime_on_specific_timezone_not_during_dst 151 | each_timezone do 152 | # Start from a time that is not subject to DST 153 | Timecop.freeze(2009, 12, 1) 154 | # Travel to a time that is also not in DST 155 | t = DateTime.parse("2009-12-11 00:38:00 +0100") 156 | Timecop.freeze(t) do 157 | assert_date_times_equal t, DateTime.now 158 | end 159 | end 160 | end 161 | 162 | def test_freeze_with_datetime_from_a_non_dst_time_to_a_dst_time 163 | each_timezone do 164 | # Start from a time that is not subject to DST 165 | Timecop.freeze(DateTime.parse("2009-12-1 00:00:00 +0100")) 166 | # Travel back to a time in DST 167 | t = DateTime.parse("2009-10-11 00:38:00 +0200") 168 | Timecop.freeze(t) do 169 | assert_date_times_equal t, DateTime.now 170 | end 171 | end 172 | end 173 | 174 | def test_freeze_with_datetime_from_a_dst_time_to_a_non_dst_time 175 | each_timezone do 176 | # Start from a time that is not subject to DST 177 | Timecop.freeze(DateTime.parse("2009-10-11 00:00:00 +0200")) 178 | # Travel back to a time in DST 179 | t = DateTime.parse("2009-12-1 00:38:00 +0100") 180 | Timecop.freeze(t) do 181 | assert_date_times_equal t, DateTime.now 182 | end 183 | end 184 | end 185 | 186 | def test_freeze_with_date_instance_works_as_expected 187 | d = Date.new(2008, 10, 10) 188 | Timecop.freeze(d) do 189 | assert_equal d, Date.today 190 | assert_equal Time.local(2008, 10, 10, 0, 0, 0), Time.now 191 | assert_date_times_equal DateTime.new(2008, 10, 10, 0, 0, 0, local_offset), DateTime.now 192 | end 193 | assert d != Date.today 194 | assert Time.local(2008, 10, 10, 0, 0, 0) != Time.now 195 | assert DateTime.new(2008, 10, 10, 0, 0, 0, local_offset) != DateTime.now 196 | end 197 | 198 | def test_freeze_with_integer_instance_works_as_expected 199 | t = Time.local(2008, 10, 10, 10, 10, 10) 200 | Timecop.freeze(t) do 201 | assert_equal t, Time.now 202 | assert_date_times_equal DateTime.new(2008, 10, 10, 10, 10, 10, local_offset), DateTime.now 203 | assert_equal Date.new(2008, 10, 10), Date.today 204 | Timecop.freeze(10) do 205 | assert_equal t + 10, Time.now 206 | assert_equal Time.local(2008, 10, 10, 10, 10, 20), Time.now 207 | assert_equal Date.new(2008, 10, 10), Date.today 208 | end 209 | end 210 | assert t != Time.now 211 | assert DateTime.new(2008, 10, 10, 10, 10, 10) != DateTime.now 212 | assert Date.new(2008, 10, 10) != Date.today 213 | end 214 | 215 | def test_exception_thrown_in_freeze_block_properly_resets_time 216 | t = Time.local(2008, 10, 10, 10, 10, 10) 217 | begin 218 | Timecop.freeze(t) do 219 | assert_equal t, Time.now 220 | raise "blah exception" 221 | end 222 | rescue 223 | assert t != Time.now 224 | assert_nil Time.send(:mock_time) 225 | end 226 | end 227 | 228 | def test_exception_thrown_in_return_block_restores_previous_time 229 | t = Time.local(2008, 10, 10, 10, 10, 10) 230 | Timecop.freeze(t) do 231 | Timecop.return { raise 'foobar' } rescue nil 232 | assert_equal t, Time.now 233 | end 234 | end 235 | 236 | def test_freeze_freezes_time 237 | t = Time.local(2008, 10, 10, 10, 10, 10) 238 | Timecop.freeze(t) do 239 | #assert Time.now < now, "If we had failed to freeze, time would have proceeded, which is what appears to have happened." 240 | new_t, new_d, new_dt = Time.now, Date.today, DateTime.now 241 | assert_equal t, new_t, "Failed to freeze time." # 2 seconds 242 | #sleep(10) 243 | assert_equal new_t, Time.now 244 | assert_equal new_d, Date.today 245 | assert_equal new_dt, DateTime.now 246 | end 247 | end 248 | 249 | def test_travel_keeps_time_moving 250 | t = Time.local(2008, 10, 10, 10, 10, 10) 251 | Timecop.travel(t) do 252 | new_now = Time.now 253 | assert_times_effectively_equal(new_now, t, 1, "Looks like we failed to actually travel time") 254 | sleep(0.25) 255 | assert_times_effectively_not_equal new_now, Time.now, 0.24, "Looks like time is not moving" 256 | end 257 | end 258 | 259 | def test_mocked_date_time_now_is_local 260 | each_timezone do 261 | t = DateTime.parse("2009-10-11 00:38:00 +0200") 262 | Timecop.freeze(t) do 263 | if ENV['TZ'] == 'UTC' 264 | assert_equal(local_offset, 0, "Local offset not be zero for #{ENV['TZ']}") 265 | else 266 | assert(local_offset, 0 != "Local offset should not be zero for #{ENV['TZ']}") 267 | end 268 | assert_equal local_offset, DateTime.now.offset, "Failed for timezone: #{ENV['TZ']}" 269 | end 270 | end 271 | end 272 | 273 | def test_scaling_keeps_time_moving_at_an_accelerated_rate 274 | t = Time.local(2008, 10, 10, 10, 10, 10) 275 | Timecop.scale(4, t) do 276 | start = Time.now 277 | assert_times_effectively_equal start, t, 1, "Looks like we failed to actually travel time" 278 | sleep(0.25) 279 | assert_times_effectively_equal Time.at((start + 4*0.25).to_f), Time.now, 0.25, "Looks like time is not moving at 4x" 280 | end 281 | end 282 | 283 | def test_scaling_returns_now_if_no_block_given 284 | t = Time.local(2008, 10, 10, 10, 10, 10) 285 | assert_times_effectively_equal t, Timecop.scale(4, t) 286 | end 287 | 288 | def test_scaling_returns_now_if_nil_supplied 289 | assert_times_effectively_equal Time.now, Timecop.scale(nil) 290 | end 291 | 292 | def test_scaling_raises_when_empty_string_supplied 293 | err = assert_raises(TypeError) do 294 | Timecop.scale("") 295 | end 296 | assert_match /String can't be coerced into Float/, err.message 297 | end 298 | 299 | def test_freeze_with_utc_time 300 | each_timezone do 301 | t = Time.utc(2008, 10, 10, 10, 10, 10) 302 | local = t.getlocal 303 | Timecop.freeze(t) do 304 | assert_equal local, Time.now, "Failed for timezone: #{ENV['TZ']}" 305 | end 306 | end 307 | end 308 | 309 | def test_freeze_without_arguments_instance_works_as_expected 310 | t = Time.local(2008, 10, 10, 10, 10, 10) 311 | Timecop.freeze(t) do 312 | assert_equal t, Time.now 313 | Timecop.freeze do 314 | assert_equal t, Time.now 315 | assert_equal Time.local(2008, 10, 10, 10, 10, 10), Time.now 316 | assert_equal Date.new(2008, 10, 10), Date.today 317 | end 318 | end 319 | assert t != Time.now 320 | end 321 | 322 | def test_destructive_methods_on_frozen_time 323 | # Use any time zone other than UTC. 324 | ENV['TZ'] = 'EST' 325 | 326 | t = Time.local(2008, 10, 10, 10, 10, 10) 327 | Timecop.freeze(t) do 328 | assert !Time.now.utc?, "Time#local failed to return a time in the local time zone." 329 | 330 | # #utc, #gmt, and #localtime are destructive methods. 331 | Time.now.utc 332 | 333 | assert !Time.now.utc?, "Failed to thwart destructive methods." 334 | end 335 | end 336 | 337 | def test_recursive_travel_maintains_each_context 338 | t = Time.local(2008, 10, 10, 10, 10, 10) 339 | Timecop.travel(2008, 10, 10, 10, 10, 10) do 340 | assert((t - Time.now).abs < 50, "Failed to travel time.") 341 | t2 = Time.local(2008, 9, 9, 9, 9, 9) 342 | Timecop.travel(2008, 9, 9, 9, 9, 9) do 343 | assert_times_effectively_equal(t2, Time.now, 1, "Failed to travel time.") 344 | assert_times_effectively_not_equal(t, Time.now, 1000, "Failed to travel time.") 345 | end 346 | assert_times_effectively_equal(t, Time.now, 2, "Failed to restore previously-traveled time.") 347 | end 348 | assert_nil Time.send(:mock_time) 349 | end 350 | 351 | def test_recursive_travel_yields_correct_time 352 | Timecop.travel(2008, 10, 10, 10, 10, 10) do 353 | Timecop.travel(2008, 9, 9, 9, 9, 9) do |inner_freeze| 354 | assert_times_effectively_equal inner_freeze, Time.now, 1, "Failed to yield current time back to block" 355 | end 356 | end 357 | end 358 | 359 | def test_recursive_travel_then_freeze 360 | t = Time.local(2008, 10, 10, 10, 10, 10) 361 | Timecop.travel(2008, 10, 10, 10, 10, 10) do 362 | assert((t - Time.now).abs < 50, "Failed to travel time.") 363 | t2 = Time.local(2008, 9, 9, 9, 9, 9) 364 | Timecop.freeze(2008, 9, 9, 9, 9, 9) do 365 | assert_equal t2, Time.now 366 | end 367 | assert_times_effectively_equal(t, Time.now, 2, "Failed to restore previously-traveled time.") 368 | end 369 | assert_nil Time.send(:mock_time) 370 | end 371 | 372 | def test_recursive_freeze_then_travel 373 | t = Time.local(2008, 10, 10, 10, 10, 10) 374 | Timecop.freeze(t) do 375 | assert_equal t, Time.now 376 | t2 = Time.local(2008, 9, 9, 9, 9, 9) 377 | Timecop.travel(t2) do 378 | assert_times_effectively_equal(t2, Time.now, 1, "Failed to travel time.") 379 | assert_times_effectively_not_equal(t, Time.now, 1000, "Failed to travel time.") 380 | end 381 | assert_equal t, Time.now 382 | end 383 | assert_nil Time.send(:mock_time) 384 | end 385 | 386 | def test_recursive_freeze_then_travel_keeps_time_moving 387 | t = Time.local(2008, 10, 10, 10, 10, 10) 388 | Timecop.freeze do 389 | Timecop.travel(t) do 390 | new_now = Time.now 391 | sleep(0.25) 392 | assert_times_effectively_not_equal new_now, Time.now, 0.24, "Travel failed to unfreeze time" 393 | end 394 | end 395 | end 396 | 397 | def test_recursive_freeze_then_scale_keeps_time_moving 398 | Timecop.freeze do 399 | Timecop.scale(1) do 400 | new_now = Time.now 401 | sleep(0.25) 402 | assert_times_effectively_not_equal new_now, Time.now, 0.24, "Scale failed to unfreeze time" 403 | end 404 | end 405 | end 406 | 407 | def test_travel_time_returns_now_if_no_block_given 408 | t_future = Time.local(2030, 10, 10, 10, 10, 10) 409 | assert_times_effectively_equal t_future, Timecop.travel(t_future) 410 | end 411 | 412 | def test_return_temporarily_returns_to_current_time_in_given_block 413 | time_after_travel = Time.local(1990, 7, 16) 414 | now = Time.now 415 | 416 | Timecop.travel(time_after_travel) 417 | 418 | assert_times_effectively_equal(time_after_travel, Time.now) 419 | Timecop.return do 420 | assert_times_effectively_equal(now, Time.now) 421 | end 422 | assert_times_effectively_equal(time_after_travel, Time.now) 423 | end 424 | 425 | def test_travel_returns_now_if_nil_supplied 426 | assert_times_effectively_equal Time.now, Timecop.travel(nil) 427 | end 428 | 429 | def test_travel_time_with_block_returns_the_value_of_the_block 430 | t_future = Time.local(2030, 10, 10, 10, 10, 10) 431 | expected = :foo 432 | actual = Timecop.travel(t_future) { expected } 433 | 434 | assert_equal expected, actual 435 | end 436 | 437 | def test_travel_raises_when_empty_string_supplied 438 | err = assert_raises(ArgumentError) do 439 | Timecop.travel("") 440 | end 441 | assert_match /no time information in \"\"/, err.message 442 | end 443 | 444 | def test_freeze_time_returns_now_if_no_block_given 445 | t_future = Time.local(2030, 10, 10, 10, 10, 10) 446 | assert_times_effectively_equal t_future, Timecop.freeze(t_future) 447 | end 448 | 449 | def test_freeze_time_with_block_returns_the_value_of_the_block 450 | t_future = Time.local(2030, 10, 10, 10, 10, 10) 451 | expected = :foo 452 | actual = Timecop.freeze(t_future) { expected } 453 | 454 | assert_equal expected, actual 455 | end 456 | 457 | def test_return_returns_nil 458 | assert_nil Timecop.return 459 | end 460 | 461 | def test_freeze_without_params 462 | Timecop.freeze 1 do 463 | current_time = Time.now 464 | Timecop.freeze do 465 | assert_equal Time.now, current_time 466 | end 467 | end 468 | end 469 | 470 | def test_freeze_returns_now_if_nil_supplied 471 | assert_times_effectively_equal Time.now, Timecop.freeze(nil) 472 | end 473 | 474 | def test_freeze_raises_when_empty_string_supplied 475 | err = assert_raises(ArgumentError) do 476 | Timecop.freeze("") 477 | end 478 | assert_match /no time information in \"\"/, err.message 479 | end 480 | 481 | def test_freeze_with_new_date 482 | date = Date.new(2012, 6, 9) 483 | Timecop.freeze(Date.new(2012, 6, 9)) do 484 | assert_equal date, Time.now.__send__(:to_date) 485 | end 486 | end 487 | 488 | def test_return_to_baseline_without_a_baseline_set_returns_to_current_time 489 | time_before_travel = Time.now 490 | Timecop.travel Time.now - 60 491 | Timecop.return_to_baseline 492 | assert times_effectively_equal(time_before_travel, Time.now) 493 | end 494 | 495 | def test_return_to_baseline_with_a_baseline_set_returns_to_baseline 496 | baseline = Time.local(1945, 10, 10, 10, 10, 10) 497 | Timecop.baseline = baseline 498 | Timecop.travel Time.now - 60 499 | time_now = Timecop.return_to_baseline 500 | assert times_effectively_equal(baseline, time_now), 501 | "expected to return to #{baseline}, but returned to #{time_now}" 502 | end 503 | 504 | def test_return_eliminates_baseline 505 | time_before_travel = Time.now 506 | Timecop.baseline = Time.local(1937, 9, 9, 9, 9, 9) 507 | Timecop.return 508 | assert times_effectively_equal(time_before_travel, Time.now) 509 | 510 | Timecop.travel(Time.now - 100) 511 | Timecop.return_to_baseline 512 | assert times_effectively_equal(time_before_travel, Time.now) 513 | end 514 | 515 | def test_mock_time_new_same_as_now 516 | date = Time.local(2011, 01, 02) 517 | Timecop.freeze date 518 | assert_equal date, Time.now 519 | assert_equal date, Time.new 520 | end 521 | 522 | def test_not_callable_send_travel 523 | assert_raises NoMethodError do 524 | Timecop.send_travel(:travel, Time.now - 100) 525 | end 526 | end 527 | 528 | def test_datetime_to_time_for_dst_to_non_dst 529 | # Start at a time subject to DST 530 | Timecop.travel(2009, 4, 1, 0, 0, 0, -4*60*60) do 531 | 532 | # Then freeze, via DateTime, at a time not subject to DST 533 | t = DateTime.new(2009,01,01,0,0,0, "-0500") 534 | Timecop.freeze(t) do 535 | 536 | # Check the current time via DateTime.now--should be what we asked for 537 | assert_date_times_equal t, DateTime.now 538 | 539 | # Then check the current time via Time.now (not DateTime.now) 540 | assert_times_effectively_equal Time.new(2009, 1, 1, 0, 0, 0, -5*60*60), Time.now 541 | end 542 | end 543 | end 544 | 545 | def test_raises_when_safe_mode_and_no_block 546 | with_safe_mode do 547 | assert_raises Timecop::SafeModeException do 548 | Timecop.freeze 549 | end 550 | end 551 | end 552 | 553 | def test_raises_when_safe_mode_and_no_block_though_previously_block_given 554 | Timecop.freeze do 555 | Timecop.freeze 556 | end 557 | 558 | with_safe_mode do 559 | assert_raises Timecop::SafeModeException do 560 | Timecop.freeze 561 | end 562 | end 563 | end 564 | 565 | def test_no_raise_when_safe_mode_and_block_used 566 | with_safe_mode do 567 | Timecop.freeze {} 568 | end 569 | end 570 | 571 | def test_no_raise_when_not_safe_mode_and_no_block 572 | with_safe_mode(false) do 573 | Timecop.freeze 574 | end 575 | end 576 | 577 | def test_no_raise_when_safe_mode_and_no_block_and_in_block_context 578 | with_safe_mode do 579 | Timecop.freeze do 580 | Timecop.freeze 581 | end 582 | end 583 | end 584 | 585 | def test_frozen_after_freeze 586 | Timecop.freeze 587 | assert Timecop.frozen? 588 | end 589 | 590 | def test_frozen_inside_freeze 591 | Timecop.freeze do 592 | assert Timecop.frozen? 593 | end 594 | end 595 | 596 | def test_not_frozen_after_return 597 | Timecop.freeze 598 | Timecop.return 599 | assert !Timecop.frozen? 600 | end 601 | 602 | def test_not_frozen_inside_scale 603 | Timecop.scale(2) do 604 | assert !Timecop.frozen? 605 | end 606 | end 607 | 608 | def test_not_travelled_and_not_scaled_inside_freeze 609 | Timecop.freeze do 610 | assert !Timecop.travelled? 611 | assert !Timecop.scaled? 612 | end 613 | end 614 | 615 | def test_travelled_and_not_scaled_inside_travel 616 | Timecop.travel(3) do 617 | assert Timecop.travelled? 618 | assert !Timecop.scaled? 619 | end 620 | end 621 | 622 | def test_scaled_and_not_travelled_inside_scale 623 | Timecop.scale(4) do 624 | assert Timecop.scaled? 625 | assert !Timecop.travelled? 626 | end 627 | end 628 | 629 | def test_not_scaled_after_scale 630 | Timecop.scale(5) 631 | Timecop.return 632 | assert !Timecop.scaled? 633 | end 634 | 635 | def test_not_travelled_after_travel 636 | Timecop.travel(6) 637 | Timecop.return 638 | assert !Timecop.travelled? 639 | end 640 | 641 | def test_not_frozen_inside_first_freeze_then_scale 642 | Timecop.freeze do 643 | assert Timecop.frozen? 644 | Timecop.scale(2) do 645 | assert !Timecop.frozen? 646 | end 647 | end 648 | end 649 | 650 | def test_not_frozen_inside_travel 651 | Timecop.travel(60) do 652 | assert !Timecop.frozen? 653 | end 654 | end 655 | 656 | def test_not_frozen_inside_first_freeze_then_travel 657 | Timecop.freeze do 658 | assert Timecop.frozen? 659 | Timecop.travel(60) do 660 | assert !Timecop.frozen? 661 | end 662 | end 663 | end 664 | 665 | def test_thread_safe_timecop_in_parallel 666 | Timecop.thread_safe = true 667 | date = Time.local(2011, 01, 02) 668 | thread = Thread.new do 669 | Timecop.freeze(date) do 670 | sleep 1 #give main thread time to run 671 | assert_equal date, Time.now 672 | end 673 | end 674 | 675 | sleep 0.25 676 | assert Time.now != date 677 | thread.join 678 | ensure 679 | Timecop.thread_safe = false 680 | end 681 | 682 | def test_thread_safe_timecop_returns_after_block 683 | Timecop.thread_safe = true 684 | date = Time.local(2017, 10, 8) 685 | 686 | Timecop.freeze(date) { } 687 | assert Time.now != date 688 | ensure 689 | Timecop.thread_safe = false 690 | end 691 | 692 | private 693 | 694 | def with_safe_mode(enabled=true) 695 | mode = Timecop.safe_mode? 696 | Timecop.safe_mode = enabled 697 | yield 698 | ensure 699 | Timecop.safe_mode = mode 700 | end 701 | end 702 | -------------------------------------------------------------------------------- /test/timecop_with_active_support_test.rb: -------------------------------------------------------------------------------- 1 | require 'date' 2 | 3 | require 'bundler/setup' 4 | require 'active_support/all' 5 | 6 | require_relative "test_helper" 7 | require 'timecop' 8 | 9 | class TestTimecopWithActiveSupport < Minitest::Test 10 | def teardown 11 | Timecop.return 12 | end 13 | 14 | def test_travel_does_not_reduce_precision_of_time 15 | Timecop.travel(1) 16 | long_ago = Time.now 17 | sleep(0.001) 18 | assert long_ago.nsec != Time.now.nsec 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/timecop_with_process_clock_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | require 'timecop' 3 | 4 | class TestTimecopWithProcessClock < Minitest::Test 5 | TIME_EPSILON = 0.001 # seconds - represents enough time for Process.clock_gettime to have advanced if not frozen 6 | 7 | def setup 8 | Timecop.mock_process_clock = true 9 | end 10 | 11 | def teardown 12 | Timecop.return 13 | Timecop.mock_process_clock = false 14 | end 15 | 16 | def test_process_clock_mock_disabled 17 | Timecop.mock_process_clock = false 18 | 19 | Timecop.freeze do 20 | refute_same(*consecutive_monotonic, "CLOCK_MONOTONIC is frozen") 21 | end 22 | end 23 | 24 | def test_process_clock_gettime_monotonic 25 | Timecop.freeze do 26 | assert_same(*consecutive_monotonic, "CLOCK_MONOTONIC is not frozen") 27 | end 28 | 29 | initial_time = monotonic 30 | Timecop.freeze(-0.5) do 31 | assert_operator(monotonic, :<, initial_time, "CLOCK_MONOTONIC is not traveling back in time") 32 | end 33 | end 34 | 35 | def test_process_clock_gettime_monotonic_with_date_freeze 36 | date = Date.new(2024, 6, 1) 37 | monotonic1 = Timecop.freeze(date) { monotonic } 38 | monotonic2 = Timecop.freeze(date) { monotonic } 39 | 40 | refute_equal(monotonic1, monotonic2, "CLOCK_MONOTONIC is not expected to freeze deterministically with a date") 41 | end 42 | 43 | def test_process_clock_gettime_realtime_with_date_freeze 44 | date = Date.new(2024, 6, 1) 45 | realtime_1 = Timecop.freeze(date) { realtime } 46 | realtime_2 = Timecop.freeze(date) { realtime } 47 | 48 | assert_equal(realtime_1, realtime_2, "CLOCK_REALTIME is expected to support freezing with a date") 49 | end 50 | 51 | def test_process_clock_gettime_units_integer 52 | Timecop.freeze do 53 | time_in_nanoseconds = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) 54 | time_in_microseconds = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) 55 | time_in_milliseconds = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) 56 | time_in_seconds = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second) 57 | 58 | assert_equal(time_in_microseconds, (time_in_nanoseconds / 10**3).to_i) 59 | assert_equal(time_in_milliseconds, (time_in_nanoseconds / 10**6).to_i) 60 | assert_equal(time_in_seconds, (time_in_nanoseconds / 10**9).to_i) 61 | end 62 | end 63 | 64 | def test_process_clock_gettime_units_float 65 | Timecop.freeze do 66 | time_in_nanoseconds = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond).to_f 67 | 68 | float_microseconds = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_microsecond) 69 | float_milliseconds = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) 70 | float_seconds = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) 71 | 72 | delta = 0.000001 73 | assert_in_delta(float_microseconds, time_in_nanoseconds / 10**3, delta) 74 | assert_in_delta(float_milliseconds, time_in_nanoseconds / 10**6, delta) 75 | assert_in_delta(float_seconds, time_in_nanoseconds / 10**9, delta) 76 | end 77 | end 78 | 79 | def test_process_clock_gettime_monotonic_nested 80 | Timecop.freeze do 81 | parent = monotonic 82 | 83 | sleep(TIME_EPSILON) 84 | 85 | delta = 0.5 86 | Timecop.freeze(delta) do 87 | child = monotonic 88 | assert_equal(child, parent + delta, "Nested freeze not working for monotonic time") 89 | end 90 | end 91 | end 92 | 93 | def test_process_clock_gettime_monotonic_travel 94 | initial_time = monotonic 95 | Timecop.travel do 96 | refute_same(*consecutive_monotonic, "CLOCK_MONOTONIC is frozen") 97 | assert_operator(monotonic, :>, initial_time, "CLOCK_MONOTONIC is not moving forward") 98 | end 99 | 100 | Timecop.travel(-0.5) do 101 | refute_same(*consecutive_monotonic, "CLOCK_MONOTONIC is frozen") 102 | assert_operator(monotonic, :<, initial_time, "CLOCK_MONOTONIC is not traveling properly") 103 | end 104 | end 105 | 106 | def test_process_clock_gettime_monotonic_scale 107 | scale = 4 108 | sleep_length = 0.25 109 | Timecop.scale(scale) do 110 | initial_time = monotonic 111 | sleep(sleep_length) 112 | expected_time = initial_time + (scale * sleep_length) 113 | assert_times_effectively_equal expected_time, monotonic, 0.1, "CLOCK_MONOTONIC is not scaling" 114 | end 115 | end 116 | 117 | def test_process_clock_gettime_realtime 118 | Timecop.freeze do 119 | assert_same(*consecutive_realtime, "CLOCK_REALTIME is not frozen") 120 | end 121 | 122 | initial_time = realtime 123 | Timecop.freeze(-20) do 124 | assert_operator(realtime, :<, initial_time, "CLOCK_REALTIME is not traveling back in time") 125 | end 126 | end 127 | 128 | def test_process_clock_gettime_realtime_travel 129 | initial_time = realtime 130 | Timecop.travel do 131 | refute_equal consecutive_realtime, "CLOCK_REALTIME is frozen" 132 | assert_operator(realtime, :>, initial_time, "CLOCK_REALTIME is not moving forward") 133 | end 134 | 135 | delta = 0.1 136 | Timecop.travel(Time.now - delta) do 137 | refute_equal consecutive_realtime, "CLOCK_REALTIME is frozen" 138 | assert_operator(realtime, :<, initial_time, "CLOCK_REALTIME is not traveling properly") 139 | sleep(delta) 140 | assert_operator(realtime, :>, initial_time, "CLOCK_REALTIME is not traveling properly") 141 | end 142 | end 143 | 144 | def test_process_clock_gettime_realtime_scale 145 | scale = 4 146 | sleep_length = 0.25 147 | Timecop.scale(scale) do 148 | initial_time = realtime 149 | sleep(sleep_length) 150 | assert_operator(initial_time + scale * sleep_length, :<, realtime, "CLOCK_REALTIME is not scaling") 151 | end 152 | end 153 | 154 | private 155 | 156 | def monotonic 157 | Process.clock_gettime(Process::CLOCK_MONOTONIC) 158 | end 159 | 160 | def realtime 161 | Process.clock_gettime(Process::CLOCK_REALTIME) 162 | end 163 | 164 | def consecutive_monotonic 165 | consecutive_times(:monotonic) 166 | end 167 | 168 | def consecutive_realtime 169 | consecutive_times(:realtime) 170 | end 171 | 172 | def consecutive_times(time_method) 173 | t1 = send(time_method) 174 | sleep(TIME_EPSILON) 175 | t2 = send(time_method) 176 | 177 | [t1, t2] 178 | end 179 | end 180 | -------------------------------------------------------------------------------- /test/timecop_without_date_but_with_time_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | require "time" 3 | 4 | class TestTimecopWithoutDateButWithTime < Minitest::Test 5 | def test_loads_properly_when_time_is_required_instead_of_date 6 | require 'timecop' 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/timecop_without_date_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | require 'timecop' 3 | 4 | class TestTimecopWithoutDate < Minitest::Test 5 | 6 | def setup 7 | Object.send(:remove_const, :Date) if Object.const_defined?(:Date) 8 | Object.send(:remove_const, :DateTime) if Object.const_defined?(:DateTime) 9 | end 10 | 11 | # just in case...let's really make sure that Timecop is disabled between tests... 12 | def teardown 13 | Timecop.return 14 | end 15 | 16 | def test_freeze_changes_and_resets_time 17 | # depending on how we're invoked (individually or via the rake test suite) 18 | assert !Time.respond_to?(:zone) || Time.zone.nil? 19 | 20 | t = Time.local(2008, 10, 10, 10, 10, 10) 21 | assert t != Time.now 22 | Timecop.freeze(2008, 10, 10, 10, 10, 10) do 23 | assert_equal t, Time.now 24 | end 25 | assert t != Time.now 26 | end 27 | 28 | def test_recursive_freeze 29 | t = Time.local(2008, 10, 10, 10, 10, 10) 30 | Timecop.freeze(2008, 10, 10, 10, 10, 10) do 31 | assert_equal t, Time.now 32 | t2 = Time.local(2008, 9, 9, 9, 9, 9) 33 | Timecop.freeze(2008, 9, 9, 9, 9, 9) do 34 | assert_equal t2, Time.now 35 | end 36 | assert_equal t, Time.now 37 | end 38 | assert_nil Time.send(:mock_time) 39 | end 40 | 41 | def test_exception_thrown_in_freeze_block_properly_resets_time 42 | t = Time.local(2008, 10, 10, 10, 10, 10) 43 | begin 44 | Timecop.freeze(t) do 45 | assert_equal t, Time.now 46 | raise "blah exception" 47 | end 48 | rescue 49 | assert t != Time.now 50 | assert_nil Time.send(:mock_time) 51 | end 52 | end 53 | 54 | def test_freeze_freezes_time 55 | t = Time.local(2008, 10, 10, 10, 10, 10) 56 | now = Time.now 57 | Timecop.freeze(t) do 58 | sleep(0.25) 59 | assert Time.now < now, "If we had failed to freeze, time would have proceeded, which is what appears to have happened." 60 | new_t = Time.now 61 | assert_equal t, new_t, "Failed to change move time." # 2 seconds 62 | assert_equal new_t, Time.now 63 | end 64 | end 65 | 66 | def test_travel_keeps_time_moving 67 | t = Time.local(2008, 10, 10, 10, 10, 10) 68 | now = Time.now 69 | Timecop.travel(t) do 70 | new_now = Time.now 71 | assert_times_effectively_equal new_now, t, 1, "Looks like we failed to actually travel time" # 0.1 seconds 72 | sleep(0.25) 73 | assert_times_effectively_not_equal new_now, Time.now, 0.24, "Looks like time is not moving" 74 | end 75 | end 76 | 77 | def test_recursive_travel_maintains_each_context 78 | t = Time.local(2008, 10, 10, 10, 10, 10) 79 | Timecop.travel(2008, 10, 10, 10, 10, 10) do 80 | assert((t - Time.now).abs < 50, "Failed to travel time.") 81 | t2 = Time.local(2008, 9, 9, 9, 9, 9) 82 | Timecop.travel(2008, 9, 9, 9, 9, 9) do 83 | assert_times_effectively_equal(t2, Time.now, 1, "Failed to travel time.") 84 | assert_times_effectively_not_equal(t, Time.now, 1000, "Failed to travel time.") 85 | end 86 | assert_times_effectively_equal(t, Time.now, 2, "Failed to restore previously-traveled time.") 87 | end 88 | assert_nil Time.send(:mock_time) 89 | end 90 | 91 | def test_recursive_travel_then_freeze 92 | t = Time.local(2008, 10, 10, 10, 10, 10) 93 | Timecop.travel(2008, 10, 10, 10, 10, 10) do 94 | assert((t - Time.now).abs < 50, "Failed to travel time.") 95 | t2 = Time.local(2008, 9, 9, 9, 9, 9) 96 | Timecop.freeze(2008, 9, 9, 9, 9, 9) do 97 | assert_equal t2, Time.now 98 | end 99 | assert_times_effectively_equal(t, Time.now, 2, "Failed to restore previously-traveled time.") 100 | end 101 | assert_nil Time.send(:mock_time) 102 | end 103 | 104 | def test_recursive_freeze_then_travel 105 | t = Time.local(2008, 10, 10, 10, 10, 10) 106 | Timecop.freeze(t) do 107 | assert_equal t, Time.now 108 | t2 = Time.local(2008, 9, 9, 9, 9, 9) 109 | Timecop.travel(t2) do 110 | assert_times_effectively_equal(t2, Time.now, 1, "Failed to travel time.") 111 | assert_times_effectively_not_equal(t, Time.now, 1000, "Failed to travel time.") 112 | end 113 | assert_equal t, Time.now 114 | end 115 | assert_nil Time.send(:mock_time) 116 | end 117 | 118 | end 119 | -------------------------------------------------------------------------------- /timecop.gemspec: -------------------------------------------------------------------------------- 1 | require File.expand_path('../lib/timecop/version', __FILE__) 2 | 3 | Gem::Specification.new do |s| 4 | s.name = %q{timecop} 5 | s.version = Timecop::VERSION 6 | s.required_ruby_version = Gem::Requirement.new(">= 2.1.0") 7 | s.authors = ["Travis Jeffery", "John Trupiano"] 8 | s.description = %q{A gem providing "time travel" and "time freezing" capabilities, making it dead simple to test time-dependent code. It provides a unified method to mock Time.now, Date.today, and DateTime.now in a single call.} 9 | s.email = %q{travisjeffery@gmail.com} 10 | s.extra_rdoc_files = [ 11 | "LICENSE", 12 | "README.markdown" 13 | ] 14 | s.files = [ 15 | "LICENSE", 16 | "README.markdown", 17 | "Rakefile", 18 | "lib/timecop.rb", 19 | "lib/timecop/time_extensions.rb", 20 | "lib/timecop/time_stack_item.rb", 21 | "lib/timecop/version.rb", 22 | "lib/timecop/timecop.rb" 23 | ] 24 | s.homepage = %q{https://github.com/travisjeffery/timecop} 25 | s.rdoc_options = ["--charset=UTF-8"] 26 | s.require_paths = ["lib"] 27 | s.summary = %q{A gem providing "time travel" and "time freezing" capabilities, making it dead simple to test time-dependent code. It provides a unified method to mock Time.now, Date.today, and DateTime.now in a single call.} 28 | s.license = "MIT" 29 | end 30 | --------------------------------------------------------------------------------