├── .github └── workflows │ └── tests.yaml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── benchmark.py ├── conftest.py ├── libfaketime ├── __init__.py └── _version.py ├── pyproject.toml ├── requirements.txt ├── setup.cfg ├── setup.py └── test ├── __init__.py ├── test_faketime.py ├── test_freezegun.py └── test_tz.py /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: tests 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | tests: 9 | name: ${{ matrix.os }} - python${{ matrix.python }} - ${{ matrix.tz }} 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | os: 15 | - ubuntu-24.04 16 | - ubuntu-22.04 17 | - macos-14 18 | - macos-13 19 | python: 20 | - '3.13' 21 | - '3.12' 22 | - '3.11' 23 | - '3.10' 24 | - '3.9' 25 | tz: 26 | - 'utc' 27 | - 'cest' 28 | steps: 29 | - uses: actions/checkout@v4 30 | - uses: actions/setup-python@v5 31 | with: 32 | python-version: ${{ matrix.python }} 33 | - uses: actions/cache@v4 34 | with: 35 | path: ~/.cache/pip 36 | key: pip|${{ hashFiles('setup.py') }}|${{ hashFiles('setup.cfg') }} 37 | - run: pip install tox 38 | - run: git submodule update --init --force 39 | - run: env FAKETIME_COMPILE_CFLAGS="-UFAKE_STAT -UFAKE_UTIME -UFAKE_SLEEP" make -C libfaketime/vendor/libfaketime 40 | if: runner.os == 'Linux' 41 | - run: make -C libfaketime/vendor/libfaketime 42 | if: runner.os == 'macOS' 43 | - run: tox -e ${{ matrix.python }}-${{ matrix.tz }} --recreate 44 | 45 | style: 46 | runs-on: ubuntu-latest 47 | steps: 48 | - uses: actions/checkout@v4 49 | - uses: pre-commit/action@v3.0.1 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pytest_cache/ 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | 45 | # Translations 46 | *.mo 47 | *.pot 48 | 49 | # Django stuff: 50 | *.log 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | # PyBuilder 56 | target/ 57 | 58 | # IntelliJ/PyCharm IDE config 59 | /.idea 60 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/libfaketime"] 2 | path = libfaketime/vendor/libfaketime 3 | url = https://github.com/wolfcw/libfaketime.git 4 | branch = python-libfaketime 5 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | repos: 3 | - repo: https://github.com/astral-sh/ruff-pre-commit 4 | rev: 'v0.8.5' 5 | hooks: 6 | - id: ruff 7 | args: [--fix, --exit-non-zero-on-fix] 8 | - id: ruff-format 9 | - repo: https://github.com/pre-commit/pre-commit-hooks 10 | rev: v5.0.0 11 | hooks: 12 | - id: fix-byte-order-marker 13 | - id: trailing-whitespace 14 | - id: end-of-file-fixer 15 | - id: check-toml 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | [Semantic versioning](http://semver.org/) is used. 5 | 6 | 3.0.0 7 | ----- 8 | released 2025-01-20 9 | 10 | Thanks for @azmeuk for their contributions to this release. 11 | 12 | - breaking: drop support for python 3.8 13 | - disable FAKETIME_FORCE_MONOTONIC_FIX to attempt to fix a performance regression in 2.1.0: [#81](https://github.com/simon-weber/python-libfaketime/issues/81) 14 | - add support for python 3.13 15 | 16 | 2.1.0 17 | ----- 18 | released 2024-05-17 19 | 20 | Thanks for @azmeuk for all their contributions to this release! 21 | 22 | - add support for timestamp files, which enables freezing time across subprocesses: [#78](https://github.com/simon-weber/python-libfaketime/pull/78) 23 | - upgrade underlying libfaketime to 0.9.10 without modifications: [#75](https://github.com/simon-weber/python-libfaketime/pull/75) 24 | - add a quiet param to rexec_if_needed: [#72](https://github.com/simon-weber/python-libfaketime/pull/72) 25 | 26 | 2.0.0 27 | ----- 28 | released 2020-04-17 29 | 30 | - breaking: drop python 2.7 support 31 | - set LD_LIBRARY_PATH on linux to support paths containing spaces: [#57](https://github.com/simon-weber/python-libfaketime/pull/57) 32 | - fix compatibility with non-pytz tzinfo objects: [#58](https://github.com/simon-weber/python-libfaketime/pull/58) 33 | 34 | 1.2.1 35 | ----- 36 | released 2019-01-20 37 | 38 | - fix a deadlock on python 3.7+ 39 | 40 | 1.2.0 41 | ----- 42 | released 2018-10-28 43 | 44 | - offset-aware datetimes now properly fake the timezone as well: [#49](https://github.com/simon-weber/python-libfaketime/pull/49) 45 | 46 | 1.1.0 47 | ----- 48 | released 2018-10-07 49 | 50 | - decorated classes can access the fake_time object with ``self._faked_time``: [#47](https://github.com/simon-weber/python-libfaketime/pull/47) 51 | 52 | 1.0.0 53 | ----- 54 | released 2018-06-16 55 | 56 | - **backwards incompatible**: the monotonic clock is no longer mocked: [#45](https://github.com/simon-weber/python-libfaketime/pull/45) 57 | - ensure TZ is set to a valid timezone: [#46](https://github.com/simon-weber/python-libfaketime/pull/46) 58 | 59 | 0.5.2 60 | ----- 61 | released 2018-05-19 62 | 63 | - fix a bug causing incorrect times after unpatching under python 3.6+: [#43](https://github.com/simon-weber/python-libfaketime/pull/43) 64 | - fix compilation under gcc8: [#44](https://github.com/simon-weber/python-libfaketime/pull/44) 65 | 66 | 0.5.1 67 | ----- 68 | released 2018-01-19 69 | 70 | - fix usage as a class decorator : [#41](https://github.com/simon-weber/python-libfaketime/pull/41) 71 | 72 | 0.5.0 73 | ----- 74 | released 2017-09-10 75 | 76 | - alias fake_time for freeze_time: [#31](https://github.com/simon-weber/python-libfaketime/pull/31) 77 | - add tz_offset parameter: [#36](https://github.com/simon-weber/python-libfaketime/pull/36) 78 | 79 | 0.4.4 80 | ----- 81 | released 2017-07-16 82 | 83 | - allow contextlib2 as an alternative to contextdecorator: [#30](https://github.com/simon-weber/python-libfaketime/pull/30) 84 | 85 | 0.4.3 86 | ----- 87 | released 2017-07-07 88 | 89 | - add macOS Sierra compatibility: [#29](https://github.com/simon-weber/python-libfaketime/pull/29) 90 | 91 | 0.4.2 92 | ----- 93 | released 2016-06-30 94 | 95 | - fix only_main_thread=False: [#24](https://github.com/simon-weber/python-libfaketime/pull/24) 96 | 97 | 0.4.1 98 | ----- 99 | released 2016-05-02 100 | 101 | - fix deadlocks from uuid.uuid1 when faking time: [#14](https://github.com/simon-weber/python-libfaketime/pull/14) 102 | - remove contextdecorator dependency on python3: [#15](https://github.com/simon-weber/python-libfaketime/pull/15) 103 | 104 | 0.4.0 105 | ----- 106 | released 2016-04-02 107 | 108 | - freezegun's tick() is now supported; see [their docs](https://github.com/spulec/freezegun/blob/f1f5148720dd715cfd6dc03bf1861dbedfaad493/README.rst#manual-ticks) for usage. 109 | 110 | 0.3.0 111 | ----- 112 | released 2016-03-04 113 | 114 | - invoking ``libfaketime`` from the command line will now print the necessary environment to avoid a re-exec. 115 | 116 | 0.2.1 117 | ----- 118 | released 2016-03-01 119 | 120 | - python 3 support 121 | 122 | 0.1.1 123 | ----- 124 | released 2015-09-11 125 | 126 | - prevent distribution of test directory: https://github.com/simon-weber/python-libfaketime/pull/4 127 | 128 | 0.1.0 129 | ----- 130 | released 2015-06-23 131 | 132 | - add global start/stop callbacks 133 | 134 | 0.0.3 135 | ----- 136 | released 2015-03-28 137 | 138 | - initial packaged release 139 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG.md LICENSE README.md 2 | recursive-include libfaketime *.py 3 | include libfaketime/vendor/libfaketime/Makefile 4 | include libfaketime/vendor/libfaketime/src/Makefile 5 | include libfaketime/vendor/libfaketime/src/Makefile.OSX 6 | include libfaketime/vendor/libfaketime/COPYING 7 | recursive-include libfaketime/vendor/libfaketime/src *.c *.h *.map 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | python-libfaketime: fast date/time mocking 2 | ========================================== 3 | 4 | [![github actions](https://github.com/simon-weber/python-libfaketime/actions/workflows/tests.yaml/badge.svg)](https://github.com/simon-weber/python-libfaketime/actions/workflows/tests.yaml) 5 | [![pypi](https://img.shields.io/pypi/v/libfaketime.svg)](https://pypi.python.org/pypi/libfaketime) 6 | [![repominder](https://img.shields.io/badge/dynamic/json.svg?label=release&query=%24.status&maxAge=43200&uri=https%3A%2F%2Fwww.repominder.com%2Fbadge%2FeyJmdWxsX25hbWUiOiAic2ltb24td2ViZXIvcHl0aG9uLWxpYmZha2V0aW1lIn0%3D%2F&link=https%3A%2F%2Fwww.repominder.com%2F)](https://www.repominder.com) 7 | 8 | python-libfaketime is a wrapper of [libfaketime](https://github.com/wolfcw/libfaketime) for python. 9 | Some brief details: 10 | 11 | * Linux and OS X, Pythons 3.8 through 3.12, pypy and pypy3 12 | * Mostly compatible with [freezegun](https://github.com/spulec/freezegun). 13 | * Microsecond resolution. 14 | * Accepts datetimes and strings that can be parsed by dateutil. 15 | * Not threadsafe. 16 | * Will break profiling. A workaround: use ``libfaketime.{begin, end}_callback`` to disable/enable your profiler ([nosetest example](https://gist.github.com/simon-weber/8d43e33448684f85718417ce1a072bc8)). 17 | 18 | 19 | Installation 20 | ------------ 21 | 22 | ```sh 23 | $ pip install libfaketime 24 | ``` 25 | 26 | Usage 27 | ----- 28 | 29 | ```python 30 | import datetime 31 | from libfaketime import fake_time, reexec_if_needed 32 | 33 | # libfaketime needs to be preloaded by the dynamic linker. 34 | # This will exec the same command, but with the proper environment variables set. 35 | # You can also skip this and manually manage your env (see "How to avoid re-exec"). 36 | reexec_if_needed() 37 | 38 | def test_datetime_now(): 39 | 40 | # fake_time can be used as a context_manager 41 | with fake_time('1970-01-01 00:00:01'): 42 | 43 | # Every calls to a date or datetime function returns the mocked date 44 | assert datetime.datetime.utcnow() == datetime.datetime(1970, 1, 1, 0, 0, 1) 45 | assert datetime.datetime.now() == datetime.datetime(1970, 1, 1, 0, 0, 1) 46 | assert time.time() == 1 47 | 48 | 49 | # fake_time can also be used as a decorator 50 | @fake_time('1970-01-01 00:00:01', tz_offset=12) 51 | def test_datetime_now_with_offset(): 52 | 53 | # datetime.utcnow returns the mocked datetime without offset 54 | assert datetime.datetime.utcnow() == datetime.datetime(1970, 1, 1, 0, 0, 1) 55 | 56 | # datetime.now returns the mocked datetime with the offset passed to fake_time 57 | assert datetime.datetime.now() == datetime.datetime(1970, 1, 1, 12, 0, 1) 58 | ``` 59 | 60 | ### remove_vars 61 | 62 | By default, ``reexec_if_needed`` removes the ``LD_PRELOAD`` variable after the 63 | re-execution, to keep your environment as clean as possible. You might want it 64 | to stick around, for example when using parallelized tests that use subprocess 65 | like ``pytest-xdist``, and simply for tests where subprocess is called. To 66 | keep them around, pass ``remove_vars=False`` like: 67 | 68 | ```python 69 | reexec_if_needed(remove_vars=False) 70 | ``` 71 | 72 | ### quiet 73 | 74 | To avoid displaying the informative text when re-executing, you can set the 75 | `quiet` parameter: 76 | 77 | ```python 78 | reexec_if_needed(quiet=True) 79 | ``` 80 | 81 | ### timestamp_file 82 | 83 | A common time can be shared between several execution contexts by using a file 84 | to store the time to mock, instead of environment variables. This is useful 85 | to control the time of a running process for instance. Here is a schematized 86 | use case: 87 | 88 | ```python 89 | reexec_if_needed(remove_vars=False) 90 | 91 | with fake_time("1970-01-01 00:00:00", timestamp_file="/tmp/timestamp"): 92 | subprocess.run("/some/server/process") 93 | 94 | with fake_time("2000-01-01 00:00:00", timestamp_file="/tmp/timestamp"): 95 | assert request_the_server_process_date() == "2000-01-01 00:00:00" 96 | ``` 97 | 98 | Performance 99 | ----------- 100 | 101 | libfaketime tends to be significantly faster than [freezegun](https://github.com/spulec/freezegun). 102 | Here's the output of a [totally unscientific benchmark](https://github.com/simon-weber/python-libfaketime/blob/master/benchmark.py) on my laptop: 103 | 104 | ```sh 105 | $ python benchmark.py 106 | re-exec with libfaketime dependencies 107 | timing 1000 executions of 108 | 0.021755 seconds 109 | 110 | $ python benchmark.py freezegun 111 | timing 1000 executions of 112 | 6.561472 seconds 113 | ``` 114 | 115 | Use with py.test 116 | ---------------- 117 | 118 | The [pytest-libfaketime](https://github.com/pytest-dev/pytest-libfaketime) plugin will automatically configure python-libfaketime for you: 119 | 120 | ```sh 121 | $ pip install pytest-libfaketime 122 | ``` 123 | 124 | Alternatively, you can reexec manually from inside the pytest_configure hook: 125 | 126 | ```python 127 | # conftest.py 128 | import os 129 | import libfaketime 130 | 131 | def pytest_configure(): 132 | libfaketime.reexec_if_needed() 133 | _, env_additions = libfaketime.get_reload_information() 134 | os.environ.update(env_additions) 135 | ``` 136 | 137 | Use with tox 138 | ------------ 139 | 140 | In your tox configuration file, under the ``testenv`` bloc, add the libfaketime environment variables to avoid re-execution: 141 | 142 | ```ini 143 | setenv = 144 | LD_PRELOAD = {envsitepackagesdir}/libfaketime/vendor/libfaketime/src/libfaketime.so.1 145 | DONT_FAKE_MONOTONIC = 1 146 | FAKETIME_DID_REEXEC = true 147 | ``` 148 | 149 | Migration from freezegun 150 | ------------------------ 151 | 152 | python-libfaketime should have the same behavior as freezegun when running on supported code. To migrate to it, you can run: 153 | 154 | ```bash 155 | find . -type f -name "*.py" -exec sed -i 's/freezegun/libfaketime/g' "{}" \; 156 | ``` 157 | 158 | How to avoid re-exec 159 | -------------------- 160 | 161 | In some cases - especially when your tests start other processes - re-execing can cause unexpected problems. To avoid this, you can preload the necessary environment variables yourself. The necessary environment for your system can be found by running ``python-libfaketime`` on the command line: 162 | 163 | ```sh 164 | $ python-libfaketime 165 | export LD_PRELOAD="/home/foo//vendor/libfaketime/src/libfaketime.so.1" 166 | export DONT_FAKE_MONOTONIC="1" 167 | export FAKETIME_NO_CACHE="1" 168 | export FAKETIME_DID_REEXEC=true 169 | ``` 170 | 171 | You can easily put this in a script like: 172 | 173 | ```sh 174 | $ eval $(python-libfaketime) 175 | $ pytest # ...or any other code that imports libfaketime 176 | ``` 177 | 178 | Contributing and testing 179 | ------------------------ 180 | 181 | Contributions are welcome! You should compile libfaketime before running tests: 182 | 183 | ```bash 184 | git submodule init --update 185 | # For Linux: 186 | env FAKETIME_COMPILE_CFLAGS="-UFAKE_STAT -UFAKE_UTIME -UFAKE_SLEEP" make -C libfaketime/vendor/libfaketime 187 | # For macOS 188 | env make -C libfaketime/vendor/libfaketime 189 | ``` 190 | 191 | Then you can install requirements with ``pip install -r requirements.txt`` and use ``pytest`` and ``tox`` to run the tests. 192 | 193 | uuid1 deadlock 194 | -------------- 195 | 196 | Calling ``uuid.uuid1()`` multiple times while in a fake_time context can result in a deadlock when an OS-level uuid library is available. 197 | To avoid this, python-libtaketime will monkeypatch uuid._uuid_generate_time (or similar, it varies by version) to None inside a fake_time context. 198 | This may slow down uuid1 generation but should not affect correctness. 199 | -------------------------------------------------------------------------------- /benchmark.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import sys 3 | import time 4 | 5 | from freezegun import freeze_time as freezegun_fake_time 6 | 7 | from libfaketime import fake_time as lft_fake_time 8 | from libfaketime import reexec_if_needed 9 | 10 | 11 | def sample(faker): 12 | start = time.perf_counter() 13 | 14 | with faker(datetime.datetime.now()): 15 | datetime.datetime.now() 16 | 17 | datetime.datetime.now() 18 | 19 | return time.perf_counter() - start 20 | 21 | 22 | if __name__ == "__main__": 23 | if len(sys.argv) > 1 and sys.argv[1] == "freezegun": 24 | faker = freezegun_fake_time 25 | else: 26 | faker = lft_fake_time 27 | reexec_if_needed() 28 | 29 | iterations = 1000 30 | 31 | print(f"timing {iterations} executions of {faker}") 32 | 33 | sum = 0 34 | for _ in range(iterations): 35 | sum += sample(faker) 36 | 37 | print(sum, "seconds") 38 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import libfaketime 4 | 5 | 6 | def pytest_configure(): 7 | libfaketime.reexec_if_needed() 8 | _, env_additions = libfaketime.get_reload_information() 9 | os.environ.update(env_additions) 10 | -------------------------------------------------------------------------------- /libfaketime/__init__.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import functools 3 | import inspect 4 | import os 5 | import sys 6 | import threading 7 | import time 8 | import unittest 9 | import uuid 10 | from copy import deepcopy 11 | 12 | import dateutil.parser 13 | from pytz import timezone 14 | from pytz import utc 15 | 16 | # When using reexec_if_needed, remove_vars=True and a test loader that purges 17 | # sys.modules (like nose), it can be tough to run reexec_if_needed only once. 18 | # This env var is set by reexec to ensure we don't reload more than once. 19 | 20 | _DID_REEXEC_VAR = "FAKETIME_DID_REEXEC" 21 | _FAKETIME_FMT = "%Y-%m-%d %T.%f" 22 | 23 | 24 | def _get_lib_path(): 25 | vendor_dir = "libfaketime" 26 | 27 | return os.path.join( 28 | os.path.dirname(__file__), os.path.join("vendor", vendor_dir, "src") 29 | ) 30 | 31 | 32 | def _get_shared_lib(basename): 33 | return os.path.join(_get_lib_path(), basename) 34 | 35 | 36 | def _setup_ld_preload(soname): 37 | if "LD_PRELOAD" in os.environ: 38 | preload = "{}:{}".format(soname, os.environ["LD_PRELOAD"]) 39 | else: 40 | preload = soname 41 | 42 | return preload 43 | 44 | 45 | # keys are the first 5 chars since we don't care about the version. 46 | _lib_addition = { 47 | "linux": { 48 | "LD_LIBRARY_PATH": _get_lib_path(), 49 | "LD_PRELOAD": _setup_ld_preload("libfaketime.so.1"), 50 | }, 51 | "darwi": { 52 | "DYLD_INSERT_LIBRARIES": _get_shared_lib("libfaketime.1.dylib"), 53 | }, 54 | } 55 | 56 | _other_additions = { 57 | "linux": { 58 | "DONT_FAKE_MONOTONIC": "1", 59 | "FAKETIME_NO_CACHE": "1", 60 | "FAKETIME_FORCE_MONOTONIC_FIX": "0", 61 | }, 62 | "darwi": { 63 | "DONT_FAKE_MONOTONIC": "1", 64 | "DYLD_FORCE_FLAT_NAMESPACE": "1", 65 | "FAKETIME_NO_CACHE": "1", 66 | "FAKETIME_FORCE_MONOTONIC_FIX": "0", 67 | }, 68 | } 69 | 70 | _env_additions = deepcopy(_lib_addition) 71 | for platform_name, d in list(_other_additions.items()): 72 | # Just doing a .update wouldn't merge the sub dictionaries. 73 | _env_additions[platform_name].update(d) 74 | 75 | 76 | def get_reload_information(): 77 | try: 78 | env_additions = _env_additions[sys.platform[:5]] 79 | except KeyError: 80 | raise RuntimeError(f"libfaketime does not support platform {sys.platform}") 81 | 82 | needs_reload = os.environ.get(_DID_REEXEC_VAR) != "true" 83 | 84 | return needs_reload, env_additions 85 | 86 | 87 | def main(): # pragma: nocover 88 | """Print the necessary environment to stdout.""" 89 | _, _env_additions = get_reload_information() 90 | for key, value in _env_additions.items(): 91 | print(f'export {key}="{value}"') 92 | print(f"export {_DID_REEXEC_VAR}=true") 93 | 94 | 95 | def reexec_if_needed(remove_vars=True, quiet=False): 96 | needs_reload, env_additions = get_reload_information() 97 | if needs_reload: 98 | new_environ = os.environ.copy() 99 | new_environ.update(env_additions) 100 | new_environ[_DID_REEXEC_VAR] = "true" 101 | args = [sys.executable, [sys.executable] + sys.argv, new_environ] 102 | if not quiet: 103 | print("re-exec with libfaketime dependencies") 104 | os.execve(*args) 105 | 106 | if remove_vars: 107 | for key in env_additions: 108 | if key in os.environ: 109 | del os.environ[key] 110 | 111 | 112 | def begin_callback(instance): 113 | """Execute custom code just before faking the time.""" 114 | pass 115 | 116 | 117 | def end_callback(instance): 118 | """Execute custom code after finished faking the time.""" 119 | pass 120 | 121 | 122 | class fake_time: 123 | def __init__( 124 | self, 125 | datetime_spec=None, 126 | only_main_thread=True, 127 | tz_offset=None, 128 | timestamp_file=None, 129 | ): 130 | self.only_main_thread = only_main_thread 131 | self.timezone_str = "UTC" 132 | if tz_offset is not None: 133 | self.timezone_str = f"Etc/GMT{-tz_offset:+}" 134 | 135 | if not datetime_spec and not timestamp_file: 136 | raise ValueError( 137 | "Either 'datetime_spec' or 'timestamp_file' must be passed." 138 | ) 139 | 140 | self.time_to_freeze = datetime_spec 141 | self.timestamp_file = timestamp_file 142 | 143 | if isinstance(datetime_spec, str): 144 | self.time_to_freeze = utc.localize( 145 | dateutil.parser.parse(datetime_spec) 146 | ).astimezone(timezone(self.timezone_str)) 147 | elif isinstance(datetime_spec, datetime.datetime): 148 | if datetime_spec.tzinfo: 149 | if tz_offset is not None: 150 | raise Exception( 151 | "Cannot set tz_offset when datetime already has timezone" 152 | ) 153 | self.timezone_str = datetime_spec.tzinfo.tzname(datetime_spec) 154 | 155 | def _should_fake(self): 156 | return ( 157 | not self.only_main_thread or threading.current_thread().name == "MainThread" 158 | ) 159 | 160 | _uuid_func_names = ( 161 | # < 3.7 162 | "_uuid_generate_time", 163 | # 3.7+ 164 | "_generate_time_safe", 165 | "_generate_time", 166 | ) 167 | 168 | def _should_patch_uuid(self): 169 | # Return the name of the uuid time generate function, or None if not present. 170 | # This must be patched to avoid uuid1 deadlocks in OS uuid libraries. 171 | if self._should_fake() and not self._prev_spec: 172 | for func_name in self._uuid_func_names: 173 | if hasattr(uuid, func_name): 174 | return func_name 175 | 176 | return None 177 | 178 | def _format_datetime(self, _datetime): 179 | return _datetime.strftime(_FAKETIME_FMT) 180 | 181 | def _update_time(self, time): 182 | if not self.timestamp_file: 183 | os.environ["FAKETIME"] = self._format_datetime(time) 184 | else: 185 | if time: 186 | with open(self.timestamp_file, "w") as fd: 187 | fd.write(self._format_datetime(time)) 188 | os.environ["FAKETIME_TIMESTAMP_FILE"] = self.timestamp_file 189 | 190 | def tick(self, delta=datetime.timedelta(seconds=1)): 191 | self.time_to_freeze += delta 192 | self._update_time(self.time_to_freeze) 193 | 194 | def __enter__(self): 195 | if self._should_fake(): 196 | begin_callback(self) 197 | self._prev_spec = os.environ.get("FAKETIME") 198 | self._prev_tz = os.environ.get("TZ") 199 | self._prev_fmt = os.environ.get("FAKETIME_FMT") 200 | self._prev_timestamp_file = os.environ.get("FAKETIME_TIMESTAMP_FILE") 201 | 202 | os.environ["TZ"] = self.timezone_str 203 | 204 | time.tzset() 205 | self._update_time(self.time_to_freeze) 206 | os.environ["FAKETIME_FMT"] = _FAKETIME_FMT 207 | 208 | func_name = self._should_patch_uuid() 209 | if func_name: 210 | self._backup_uuid_generate_time = getattr(uuid, func_name) 211 | setattr(uuid, func_name, None) 212 | 213 | return self 214 | 215 | def __exit__(self, *exc): 216 | func_name = self._should_patch_uuid() 217 | if func_name: 218 | setattr(uuid, func_name, self._backup_uuid_generate_time) 219 | 220 | if self._should_fake(): 221 | if self._prev_tz is not None: 222 | os.environ["TZ"] = self._prev_tz 223 | else: 224 | del os.environ["TZ"] 225 | time.tzset() 226 | 227 | if self.timestamp_file: 228 | if self._prev_timestamp_file is not None: 229 | os.environ["FAKETIME_TIMESTAMP_FILE"] = self._prev_timestamp_file 230 | elif "FAKETIME_TIMESTAMP_FILE" in os.environ: 231 | del os.environ["FAKETIME_TIMESTAMP_FILE"] 232 | 233 | else: 234 | if self._prev_spec is not None: 235 | os.environ["FAKETIME"] = self._prev_spec 236 | else: 237 | del os.environ["FAKETIME"] 238 | 239 | if self._prev_fmt is not None: 240 | os.environ["FAKETIME_FMT"] = self._prev_spec 241 | else: 242 | del os.environ["FAKETIME_FMT"] 243 | 244 | end_callback(self) 245 | 246 | return False 247 | 248 | # Freezegun compatibility. 249 | start = __enter__ 250 | stop = __exit__ 251 | 252 | # Decorator-style use support (shamelessly taken from freezegun, see 253 | # https://github.com/spulec/freezegun/blob/7ad16a5579b28fc939a69cc04f0e99ba5e87b206/freezegun/api.py#L323) 254 | 255 | def __call__(self, func): 256 | if inspect.isclass(func): 257 | return self.decorate_class(func) 258 | return self.decorate_callable(func) 259 | 260 | def decorate_class(self, klass): 261 | if issubclass(klass, unittest.TestCase): 262 | # If it's a TestCase, we assume you want to freeze the time for the 263 | # tests, from setUpClass to tearDownClass 264 | 265 | orig_setUpClass = getattr(klass, "setUpClass", None) 266 | orig_tearDownClass = getattr(klass, "tearDownClass", None) 267 | 268 | @classmethod 269 | def setUpClass(cls): 270 | self.start() 271 | if orig_setUpClass is not None: 272 | orig_setUpClass() 273 | 274 | @classmethod 275 | def tearDownClass(cls): 276 | if orig_tearDownClass is not None: 277 | orig_tearDownClass() 278 | self.stop() 279 | 280 | klass.setUpClass = setUpClass 281 | klass.tearDownClass = tearDownClass 282 | 283 | else: 284 | seen = set() 285 | 286 | klasses = ( 287 | klass.mro() 288 | if hasattr(klass, "mro") 289 | else [klass] + list(klass.__bases__) 290 | ) 291 | for base_klass in klasses: 292 | for attr, attr_value in base_klass.__dict__.items(): 293 | if attr.startswith("_") or attr in seen: 294 | continue 295 | seen.add(attr) 296 | 297 | if not callable(attr_value) or inspect.isclass(attr_value): 298 | continue 299 | 300 | try: 301 | setattr(klass, attr, self(attr_value)) 302 | except (AttributeError, TypeError): 303 | # Sometimes we can't set this for built-in types and 304 | # custom callables 305 | continue 306 | 307 | klass._faked_time = self 308 | return klass 309 | 310 | def decorate_callable(self, func): 311 | def wrapper(*args, **kwargs): 312 | with self: 313 | result = func(*args, **kwargs) 314 | return result 315 | 316 | functools.update_wrapper(wrapper, func) 317 | 318 | return wrapper 319 | 320 | 321 | freeze_time = fake_time 322 | -------------------------------------------------------------------------------- /libfaketime/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "3.0.0" 2 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.ruff.lint] 2 | select = [ 3 | "D", # pydocstyle 4 | "E", # pycodestyle 5 | "F", # pyflakes 6 | "I", # isort 7 | "UP", # pyupgrade 8 | ] 9 | ignore = [ 10 | "D100", # public module 11 | "D101", # public class 12 | "D102", # public method 13 | "D103", # public function 14 | "D104", # public package 15 | "D105", # magic method 16 | "D106", # nested class 17 | "D107", # public init 18 | "D203", # no-blank-line-before-class 19 | "D213", # multi-line-summary-second-line 20 | ] 21 | 22 | [tool.ruff.lint.isort] 23 | force-single-line = true 24 | 25 | [tool.ruff.format] 26 | docstring-code-format = true 27 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | contextdecorator==0.10.0 2 | freezegun==1.5.0 3 | mock==5.1.0 4 | pytest==8.2.0 5 | python-dateutil==2.9.0post0 6 | pytz==2024.1 7 | tox==4.15.0 8 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [tox:tox] 2 | envlist = py{39,310,311,312,313,py,py3}-{utc,cest} 3 | 4 | [testenv] 5 | setenv = 6 | PYTHONDONTWRITEBYTECODE=1 7 | utc: TZ=UTC 8 | cest: TZ=CEST 9 | 10 | deps = -rrequirements.txt 11 | commands = pytest -vs --showlocals {posargs} 12 | 13 | [testenv:style] 14 | commands = 15 | pip install pre-commit 16 | pre-commit run --all-files 17 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import re 5 | import subprocess 6 | import sys 7 | 8 | from setuptools import find_packages 9 | from setuptools import setup 10 | from setuptools.command.install import install 11 | 12 | # This hack is from http://stackoverflow.com/a/7071358/1231454; 13 | # the version is kept in a seperate file and gets parsed - this 14 | # way, setup.py doesn't have to import the package. 15 | 16 | VERSIONFILE = "libfaketime/_version.py" 17 | 18 | version_line = open(VERSIONFILE).read() 19 | version_re = r"^__version__ = ['\"]([^'\"]*)['\"]" 20 | match = re.search(version_re, version_line, re.M) 21 | if match: 22 | version = match.group(1) 23 | else: 24 | raise RuntimeError(f"Could not find version in '{VERSIONFILE}'") 25 | 26 | 27 | _vendor_path = "libfaketime/vendor/libfaketime" 28 | if sys.platform == "linux" or sys.platform == "linux2": 29 | libname = "libfaketime.so.1" 30 | elif sys.platform == "darwin": 31 | libname = "libfaketime.1.dylib" 32 | 33 | else: 34 | raise RuntimeError("libfaketime does not support platform {sys.platform}") 35 | 36 | faketime_lib = os.path.join(_vendor_path, "src", libname) 37 | 38 | 39 | class CustomInstall(install): 40 | def run(self): 41 | self.my_outputs = [] 42 | if sys.platform in ("linux", "linux2"): 43 | subprocess.check_call( 44 | [ 45 | "env", 46 | "FAKETIME_COMPILE_CFLAGS=-UFAKE_STAT -UFAKE_UTIME -UFAKE_SLEEP", 47 | "make", 48 | "-C", 49 | _vendor_path, 50 | ] 51 | ) 52 | elif sys.platform == "darwin": 53 | subprocess.check_call(["make", "-C", _vendor_path]) 54 | 55 | dest = os.path.join(self.install_purelib, os.path.dirname(faketime_lib)) 56 | try: 57 | os.makedirs(dest) 58 | except OSError as e: 59 | if e.errno != 17: 60 | raise 61 | print(faketime_lib, "->", dest) 62 | self.copy_file(faketime_lib, dest) 63 | self.my_outputs.append(os.path.join(dest, libname)) 64 | 65 | install.run(self) 66 | 67 | def get_outputs(self): 68 | outputs = install.get_outputs(self) 69 | outputs.extend(self.my_outputs) 70 | return outputs 71 | 72 | 73 | setup( 74 | name="libfaketime", 75 | version=version, 76 | author="Simon Weber", 77 | author_email="simon@simonmweber.com", 78 | url="http://pypi.python.org/pypi/libfaketime/", 79 | packages=find_packages(exclude=["test"]), 80 | scripts=[], 81 | license="GPLv2", 82 | description="A fast alternative to freezegun that wraps libfaketime.", 83 | long_description=(open("README.md").read() + "\n\n" + open("CHANGELOG.md").read()), 84 | long_description_content_type="text/markdown", 85 | install_requires=[ 86 | "python-dateutil >= 1.3", 87 | "pytz", # for pytz.timezone and pytz.utc 88 | ], 89 | classifiers=[ 90 | "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", 91 | "Development Status :: 5 - Production/Stable", 92 | "Intended Audience :: Developers", 93 | "Operating System :: OS Independent", 94 | "Programming Language :: Python", 95 | "Programming Language :: Python :: 3.9", 96 | "Programming Language :: Python :: 3.10", 97 | "Programming Language :: Python :: 3.11", 98 | "Programming Language :: Python :: 3.12", 99 | "Programming Language :: Python :: 3.13", 100 | "Programming Language :: Python :: Implementation :: CPython", 101 | "Programming Language :: Python :: Implementation :: PyPy", 102 | "Topic :: Software Development :: Libraries :: Python Modules", 103 | ], 104 | include_package_data=True, 105 | zip_safe=False, 106 | cmdclass={"install": CustomInstall}, 107 | entry_points={ 108 | "console_scripts": [ 109 | "python-libfaketime = libfaketime:main", 110 | ] 111 | }, 112 | ) 113 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simon-weber/python-libfaketime/ab69b95087efd103026467a34ffb6f92951733a1/test/__init__.py -------------------------------------------------------------------------------- /test/test_faketime.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import os 3 | import time 4 | import uuid 5 | from unittest.mock import patch 6 | 7 | import pytest 8 | 9 | import libfaketime 10 | from libfaketime import fake_time 11 | from libfaketime import freeze_time 12 | 13 | 14 | class TestReexec: 15 | @patch("os.execve") 16 | @patch("sys.platform", "win32") 17 | def test_reexec_windows_fails(self, exec_patch): 18 | with pytest.raises(RuntimeError): 19 | libfaketime.reexec_if_needed() 20 | 21 | 22 | class TestFaketime: 23 | def _assert_time_not_faked(self): 24 | # This just makes sure that non-faked time is dynamic; 25 | # I can't think of a good way to check that the non-faked time is "real". 26 | 27 | first = datetime.datetime.now().microsecond 28 | time.sleep(0.000001) 29 | second = datetime.datetime.now().microsecond 30 | 31 | assert second > first 32 | 33 | def test_fake_time_tick(self): 34 | with fake_time("2000-01-01 10:00:05") as fake: 35 | assert datetime.datetime(2000, 1, 1, 10, 0, 5) == datetime.datetime.now() 36 | fake.tick(delta=datetime.timedelta(hours=1)) 37 | assert datetime.datetime(2000, 1, 1, 11, 0, 5) == datetime.datetime.now() 38 | 39 | def test_nonfake_time_is_dynamic(self): 40 | self._assert_time_not_faked() 41 | 42 | @fake_time(datetime.datetime.now()) 43 | def test_fake_time_is_static(self): 44 | first = datetime.datetime.now().microsecond 45 | second = datetime.datetime.now().microsecond 46 | 47 | assert second == first 48 | 49 | @fake_time("2000-01-01 10:00:05") 50 | def test_fake_time_parses_easy_strings(self): 51 | assert datetime.datetime(2000, 1, 1, 10, 0, 5) == datetime.datetime.now() 52 | assert datetime.datetime(2000, 1, 1, 10, 0, 5) == datetime.datetime.utcnow() 53 | 54 | def test_fake_time_parses_easy_strings_with_timezones(self): 55 | with fake_time("2000-01-01 10:00:05", tz_offset=3): 56 | assert datetime.datetime(2000, 1, 1, 13, 0, 5) == datetime.datetime.now() 57 | assert datetime.datetime(2000, 1, 1, 10, 0, 5) == datetime.datetime.utcnow() 58 | 59 | with fake_time("2000-01-01 10:00:05", tz_offset=-3): 60 | assert datetime.datetime(2000, 1, 1, 7, 0, 5) == datetime.datetime.now() 61 | assert datetime.datetime(2000, 1, 1, 10, 0, 5) == datetime.datetime.utcnow() 62 | 63 | @fake_time("march 1st, 2014 at 1:59pm") 64 | def test_fake_time_parses_tough_strings(self): 65 | assert datetime.datetime(2014, 3, 1, 13, 59) == datetime.datetime.now() 66 | 67 | @fake_time(datetime.datetime(2014, 1, 1, microsecond=123456)) 68 | def test_fake_time_has_microsecond_granularity(self): 69 | assert ( 70 | datetime.datetime(2014, 1, 1, microsecond=123456) == datetime.datetime.now() 71 | ) 72 | 73 | def test_nested_fake_time(self): 74 | self._assert_time_not_faked() 75 | 76 | with fake_time("1/1/2000"): 77 | assert datetime.datetime(2000, 1, 1) == datetime.datetime.now() 78 | 79 | with fake_time("1/1/2001"): 80 | assert datetime.datetime(2001, 1, 1) == datetime.datetime.now() 81 | 82 | assert datetime.datetime(2000, 1, 1) == datetime.datetime.now() 83 | 84 | self._assert_time_not_faked() 85 | 86 | def test_freeze_time_alias(self): 87 | with freeze_time("2000-01-01 10:00:05"): 88 | assert datetime.datetime(2000, 1, 1, 10, 0, 5) == datetime.datetime.now() 89 | 90 | def test_monotonic_not_mocked(self): 91 | assert os.environ["DONT_FAKE_MONOTONIC"] == "1" 92 | 93 | def test_timestmap_file(self, tmpdir): 94 | file_path = str(tmpdir / "faketime.rc") 95 | 96 | with fake_time("2000-01-01 10:00:05", timestamp_file=file_path) as fake: 97 | assert datetime.datetime(2000, 1, 1, 10, 0, 5) == datetime.datetime.now() 98 | with open(file_path) as fd: 99 | assert fd.read() == "2000-01-01 10:00:05.000000" 100 | 101 | fake.tick(delta=datetime.timedelta(hours=1)) 102 | assert datetime.datetime(2000, 1, 1, 11, 0, 5) == datetime.datetime.now() 103 | with open(file_path) as fd: 104 | assert fd.read() == "2000-01-01 11:00:05.000000" 105 | 106 | with fake_time(timestamp_file=file_path): 107 | assert datetime.datetime(2000, 1, 1, 11, 0, 5) == datetime.datetime.now() 108 | 109 | 110 | class TestUUID1Deadlock: 111 | @fake_time(datetime.datetime.now()) 112 | def test_uuid1_does_not_deadlock(self): 113 | """Check the compatibility of uuid1. 114 | 115 | This test will deadlock if we fail to patch a system level uuid library. 116 | """ 117 | for i in range(100): 118 | uuid.uuid1() 119 | 120 | 121 | @fake_time("2000-01-01") 122 | class TestClassDecorator: 123 | def test_simple(self): 124 | assert datetime.datetime(2000, 1, 1) == datetime.datetime.now() 125 | self._faked_time.tick() 126 | assert datetime.datetime(2000, 1, 1, 0, 0, 1) == datetime.datetime.now() 127 | 128 | @fake_time("2001-01-01") 129 | def test_overwrite_with_func_decorator(self): 130 | assert datetime.datetime(2001, 1, 1) == datetime.datetime.now() 131 | -------------------------------------------------------------------------------- /test/test_freezegun.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import time 3 | 4 | import pytest 5 | from freezegun import freeze_time 6 | 7 | from libfaketime import fake_time 8 | 9 | # TODO 10 | # - Fix time.localtime 11 | # - Fix time.strftime 12 | 13 | # mark these as being used (via eval) 14 | assert time 15 | assert datetime 16 | 17 | test_functions = [ 18 | ("datetime.datetime.now", (), {}), 19 | ("datetime.datetime.utcnow", (), {}), 20 | ("time.time", (), {}), 21 | # ("time.localtime", (), {}), 22 | # ("time.strftime", ("%Y-%m-%d %H:%M:%S %Z",), {}), 23 | ("datetime.date", (), {"year": 1970, "month": 1, "day": 1}), 24 | ("datetime.datetime", (), {"year": 1970, "month": 1, "day": 1}), 25 | ] 26 | 27 | 28 | @pytest.mark.parametrize("test_function", test_functions) 29 | @pytest.mark.parametrize("tz_offset", [0, 12]) 30 | @pytest.mark.parametrize("date_to_freeze", ["1970-01-01 00:00:01"]) 31 | def test_compare_against_freezegun_results(test_function, tz_offset, date_to_freeze): 32 | func_name, args, kwargs = test_function 33 | 34 | with fake_time(date_to_freeze, tz_offset=tz_offset): 35 | libfaketime_result = eval(func_name)(*args, **kwargs) 36 | 37 | with freeze_time(date_to_freeze, tz_offset=tz_offset): 38 | freezegun_result = eval(func_name)(*args, **kwargs) 39 | 40 | assert freezegun_result == libfaketime_result 41 | -------------------------------------------------------------------------------- /test/test_tz.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import os 3 | 4 | import dateutil.tz 5 | import pytest 6 | from pytz import timezone 7 | 8 | from libfaketime import fake_time 9 | 10 | 11 | def test_timezone_is_restored_after_context_manager_usage(): 12 | """Check that timezones are restored when faketime context manager are closed. 13 | 14 | https://github.com/simon-weber/python-libfaketime/issues/43 15 | """ 16 | now1 = datetime.datetime.now() 17 | utcnow1 = datetime.datetime.utcnow() 18 | 19 | with fake_time(now1): 20 | datetime.datetime.now() 21 | 22 | now2 = datetime.datetime.now() 23 | utcnow2 = datetime.datetime.utcnow() 24 | 25 | assert abs((now2 - now1).total_seconds()) < 10 26 | assert abs((utcnow2 - utcnow1).total_seconds()) < 10 27 | 28 | 29 | def test_tzinfo_is_normalized(): 30 | """Ensure utcnow() behaves correctly when faking non-UTC timestamps.""" 31 | timezone_to_test_with = timezone("Europe/Brussels") 32 | time_to_freeze = timezone_to_test_with.localize( 33 | datetime.datetime(2017, 1, 2, 15, 2) 34 | ) 35 | 36 | with fake_time(time_to_freeze): 37 | # The timeshift of Europe/Brussels is UTC+1 in January 38 | assert datetime.datetime.now() == datetime.datetime(2017, 1, 2, 15, 2) 39 | assert datetime.datetime.utcnow() == datetime.datetime(2017, 1, 2, 14, 2) 40 | 41 | 42 | def test_block_setting_of_conflicting_tz_info(): 43 | """Cannot pass in tz_offset when the timestamp already carries a timezone.""" 44 | with pytest.raises(Exception) as exc_info: 45 | timezone_to_test_with = timezone("America/Havana") 46 | time_to_freeze = timezone_to_test_with.localize( 47 | datetime.datetime(2012, 10, 2, 21, 38) 48 | ) 49 | 50 | with fake_time(time_to_freeze, tz_offset=5): 51 | pass 52 | 53 | assert ( 54 | str(exc_info.value) == "Cannot set tz_offset when datetime already has timezone" 55 | ) 56 | 57 | 58 | @pytest.mark.parametrize("offset", range(-2, 3)) 59 | def test_generated_tz_is_valid(offset): 60 | """Check that generated timezones are valid. 61 | 62 | https://github.com/simon-weber/python-libfaketime/issues/46 63 | """ 64 | now = datetime.datetime.now() 65 | 66 | with fake_time(now, tz_offset=offset): 67 | fake_tz = os.environ["TZ"] 68 | timezone(fake_tz) # should not raise pytzdata.exceptions.TimezoneNotFound 69 | 70 | 71 | def test_dateutil_tz_is_valid(): 72 | test_dt = datetime.datetime(2017, 1, 2, 15, 2) 73 | dateutil_tzinfo = dateutil.tz.gettz("UTC") 74 | dt_dateutil_tzinfo = test_dt.replace(tzinfo=dateutil_tzinfo) 75 | 76 | # Should be compatible with a dateutil tzinfo object, not just pytz 77 | with fake_time(dt_dateutil_tzinfo): 78 | assert datetime.datetime.now(tz=dateutil_tzinfo) == dt_dateutil_tzinfo 79 | --------------------------------------------------------------------------------