├── .coveragerc ├── .coveralls.yml ├── .github └── workflows │ └── python-publish.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.rst ├── setup.py └── unixtimestampfield ├── __init__.py ├── admin.py ├── fields.py ├── models.py ├── submiddleware.py ├── templatetags ├── __init__.py └── unixtimestampfield.py ├── tests.py └── views.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = true 3 | source = unixtimestampfield 4 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | push: 8 | tags: 9 | - v* 10 | release: 11 | types: [created] 12 | 13 | jobs: 14 | deploy: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Set up Python 21 | uses: actions/setup-python@v2 22 | with: 23 | python-version: '3.x' 24 | - name: Install dependencies 25 | run: | 26 | python -m pip install --upgrade pip 27 | pip install setuptools wheel twine 28 | - name: Build and publish 29 | env: 30 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 31 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 32 | run: | 33 | python setup.py sdist bdist_wheel 34 | twine upload dist/* 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | # Env 60 | .idea 61 | .venv 62 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 3.5 4 | - 3.6 5 | - 3.7 6 | - 3.8 7 | 8 | env: 9 | - DJANGO=2.2.9 10 | - DJANGO=3.0.1 11 | 12 | jobs: 13 | exclude: 14 | - python: 3.5 15 | env: DJANGO=3.0.1 16 | - python: 3.8 17 | env: DJANGO=2.2.9 18 | 19 | install: 20 | - pip install -q django==$DJANGO pytz 21 | - pip install coveralls 22 | 23 | script: 24 | coverage run --source=unixtimestampfield setup.py test 25 | 26 | after_success: 27 | coveralls 28 | 29 | notifications: 30 | email: 31 | recipients: 32 | - ymy1019@gmail.com 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Garfield.yang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | django-unixtimestampfield 2 | =========================== 3 | 4 | .. image:: https://img.shields.io/travis/myyang/django-unixtimestampfield.svg 5 | :target: https://travis-ci.org/myyang/django-unixtimestampfield 6 | 7 | .. image:: https://img.shields.io/pypi/v/django-unixtimestampfield.svg 8 | :target: https://pypi.python.org/pypi/django-unixtimestampfield/ 9 | 10 | .. image:: https://coveralls.io/repos/myyang/django-unixtimestampfield/badge.svg?service=github 11 | :target: https://coveralls.io/github/myyang/django-unixtimestampfield 12 | 13 | Provide a custom field that is stored as float (UTC POSIX timestamp) and used as datetime instance. 14 | 15 | 16 | Requirements and Compatibility 17 | ------------------------------ 18 | 19 | Database that supports **Float** type is compatible. 20 | 21 | Currently tested with metrics: 22 | 23 | +---------------+-----+-----+-----+-----+ 24 | | Django/Python | 3.5 | 3.6 | 3.7 | 3.8 | 25 | +---------------+-----+-----+-----+-----+ 26 | | 2.2.x | v | v | v | | 27 | +---------------+-----+-----+-----+-----+ 28 | | 3.0.x | | v | v | v | 29 | +---------------+-----+-----+-----+-----+ 30 | 31 | * Note: for Python2 and Django1.X, please use v0.3.9 or previous version. 32 | 33 | 34 | Install 35 | ------- 36 | 37 | .. code-block:: shell 38 | 39 | pip install django-unixtimestampfield 40 | 41 | Usage 42 | ----- 43 | 44 | 45 | Used in model as following: 46 | 47 | .. code-block:: python 48 | 49 | from django.db import models 50 | 51 | from unixtimestampfield.fields import UnixTimeStampField 52 | 53 | class ModelA(models.Model): 54 | 55 | created = UnixTimeStampField(auto_now_add=True) 56 | modified = UnixTimeStampField(auto_now=True) 57 | str_ini = UnixTimeStampField(default='0.0') 58 | float_ini = UnixTimeStampField(default=0.0) 59 | int_ini = UnixTimeStampField(default=0.0) 60 | dt_ini = UnixTimeStampField(default=timezone.now) 61 | num_field = UnixTimeStampField(use_numeric=True, default=0.0) 62 | 63 | 64 | Operation exmpale: 65 | 66 | .. code-block:: python 67 | 68 | >>> m = modelA.objects.create() 69 | >>> m.created 70 | datetime.datetime(2015, 9, 2, 10, 41, 41, 937257, tzinfo=) 71 | >>> m.int_ini 72 | datetime.datetime(1970, 1, 1, 0, 0, tzinfo=) 73 | >>> m.int_ini = 3 74 | >>> m.save() 75 | >>> m.int_ini 76 | datetime.datetime(1970, 1, 1, 0, 3, tzinfo=) 77 | >>> m.num_field 78 | 0.0 79 | 80 | Field Options 81 | ~~~~~~~~~~~~~ 82 | 83 | * **auto_now**: Set to True for updating while saving, just like DatetimeField 84 | * **auto_now_add**: set to True for updating while creating, just like DatetimeField 85 | * **round_to**: percision (*num*) of round(value, *num*), default: **6** 86 | * **use_float**: **DEPRECATED in v0.3**, see use_numeric 87 | * **use_numeric**: set as True that instance attribute would be numeric, default as **False** 88 | 89 | 90 | Django settings 91 | ~~~~~~~~~~~~~~~ 92 | 93 | 94 | If `USE_TZ` is set to `False`, return current datetime (in UTC timezone) info without **tzinfo** while accessing attribute. 95 | 96 | Example: 97 | 98 | .. code-block:: python 99 | 100 | # In settings.py 101 | USE_TZ = False 102 | 103 | >>> m = modelA.objects.create() 104 | >>> m.created 105 | datetime.datetime(2015, 9, 2, 10, 41, 41, 937257) 106 | 107 | Template Tags 108 | ~~~~~~~~~~~~~ 109 | 110 | Load template tags: 111 | 112 | .. code-block:: html 113 | 114 | {% load unixtimestampfield %} 115 | 116 | 117 | Two django template filter tags are available: 118 | 119 | * **to_datetime**: Filter value as datetime 120 | * **to_timestamp**: Filter value as timestamp 121 | 122 | 123 | Tricky Sub-middleware 124 | ~~~~~~~~~~~~~~~~~~~~~ 125 | 126 | Due to value is stored as float, it's hard for recognizing and leads to this tricky middleware. 127 | 128 | Here are 3 modes to show data: 129 | 130 | * **usf_default**: Show data by default, according to use_numeric option of field. This is also default setting. 131 | * **usf_datetime**: Always convert to datetime object 132 | * **usf_timestamp**: Always convert to timestamp 133 | 134 | Use `USF_FORMAT` to indicate display police in `settings.py`. Let's see examples. 135 | 136 | Assume ModelB as: 137 | 138 | .. code-block:: python 139 | 140 | class ModelB(models.Model): 141 | 142 | num_field = UnixTimeStampField(use_numeric=True, default=0.0) 143 | dt_field = UnixTimeStampField(default=0.0) 144 | 145 | Then getting field value what you want: 146 | 147 | .. code-block:: python 148 | 149 | >>> m = ModelB() 150 | # with USF_FORMAT='usf_default' in settings.py 151 | >>> m.num_field, m.dt_field 152 | (0.0, datetime.datetime(1970, 1, 1, 0, 0)) 153 | 154 | # with USF_FORMAT='usf_datetime' in settings.py 155 | >>> m.num_field, m.dt_field 156 | (datetime.datetime(1970, 1, 1, 0, 0), datetime.datetime(1970, 1, 1, 0, 0)) 157 | 158 | # with USF_FORMAT='usf_timestamp' in settings.py 159 | >>> m.num_field, m.dt_field 160 | (0.0, 0.0) 161 | 162 | 163 | Version 164 | ------- 165 | 166 | *v0.4.0* -- Fix Python and Django compatiblity, check related section 167 | 168 | *v0.3.9* -- Fix packages including in setup.py 169 | 170 | *v0.3.8* -- Bugs fixed: Apply submiddleware to auto_now field and check format in submiddleware 171 | 172 | *V0.3.7* -- Check minimum value. 173 | 174 | *V0.3.6* -- Fix timezone problem. All records are stored UTC timezone and convert while retrive. 175 | 176 | *V0.3.5.1* -- Integer compatibility and fix timezone problem 177 | 178 | *V0.3.5* -- Parse time format: YYYY-mm-dd HH:MM:SS[.FFFFFF] 179 | 180 | *V0.3.4* -- Bugs fixed. 181 | 182 | *V0.3.3* -- Add sub-middleware and template tags 183 | 184 | *v0.3* -- Add ordinal time field and change field options **use_float** to **use_numeric**!!! 185 | 186 | *v0.2* -- Handle formfield and add options while init 187 | 188 | *v0.1* -- Added UnixTimeStampField 189 | 190 | LICENSE 191 | ------- 192 | 193 | MIT 194 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | from setuptools import setup, find_packages 3 | except ImportError: 4 | from distutils.core import setup, find_packages 5 | 6 | from distutils.core import Command 7 | 8 | """ 9 | Copied and stole from 10 | 1. https://github.com/bradjasper/django-jsonfield/blob/master/setup.py 11 | 2. http://stackoverflow.com/a/3851333 12 | """ 13 | 14 | 15 | class DjangoVerionError(Exception): 16 | pass 17 | 18 | 19 | class TestCommand(Command): 20 | user_options = [] 21 | 22 | def initialize_options(self): 23 | pass 24 | 25 | def finalize_options(self): 26 | pass 27 | 28 | def run(self): 29 | from django.conf import settings 30 | from django.apps import apps 31 | 32 | settings.configure( 33 | DATABASES={ 34 | 'default': { 35 | 'NAME': ':memory:', 36 | 'ENGINE': 'django.db.backends.sqlite3', 37 | }, 38 | }, 39 | INSTALLED_APPS=[ 40 | 'unixtimestampfield', 41 | ], 42 | TEMPLATES=[ 43 | { 44 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 45 | 'DIRS': [], 46 | 'APP_DIRS': True, 47 | }, 48 | ] 49 | ) 50 | apps.populate(settings.INSTALLED_APPS) 51 | 52 | import sys 53 | from django.test.utils import get_runner 54 | 55 | tr = get_runner(settings)() 56 | failures = tr.run_tests(['unixtimestampfield', ]) 57 | if failures: 58 | sys.exit(bool(failures)) 59 | 60 | 61 | setup(name='django-unixtimestampfield', 62 | version='0.4.0', 63 | packages=find_packages(), 64 | license='MIT', 65 | author='Garfield.Yang', 66 | author_email='ymy1019@gmail.com', 67 | url='https://github.com/myyang/django-unixtimestampfield', 68 | description='Django Unix timestamp (POSIX type) field', 69 | long_description=open("README.rst").read(), 70 | cmdclass={'test': TestCommand}, 71 | install_requires=['django>=2.2', 'six>=1.14.0', ], 72 | classifiers=[ 73 | 'Development Status :: 4 - Beta', 74 | 'License :: OSI Approved :: MIT License', 75 | 'Intended Audience :: Developers', 76 | 'Programming Language :: Python', 77 | 'Framework :: Django', 78 | ], 79 | ) 80 | -------------------------------------------------------------------------------- /unixtimestampfield/__init__.py: -------------------------------------------------------------------------------- 1 | from .fields import UnixTimeStampField 2 | -------------------------------------------------------------------------------- /unixtimestampfield/admin.py: -------------------------------------------------------------------------------- 1 | #from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /unixtimestampfield/fields.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | """ 4 | UnixTimeStampField 5 | 6 | release |release|, version |version| 7 | 8 | .. versionadded:: 0.4.0 9 | 10 | Import Six library from https://pypi.org/project/six/. 11 | 12 | .. versionadded:: 0.3.7 13 | 14 | Check minimum value. 15 | 16 | .. versionadded:: 0.3.6 17 | 18 | Fix timezone problem. All records are stored UTC timezone and convert while retrive. 19 | 20 | .. versionadded:: 0.3.5.1 21 | 22 | Integer compatibility and fix timezone problem 23 | 24 | .. versionadded:: 0.3.5 25 | 26 | Parse time format: YYYY-mm-dd HH:MM:SS[.FFFFFF] 27 | 28 | .. versionadded:: 0.3.4 29 | 30 | Bugs fixed. 31 | 32 | .. versionadded:: 0.3.3 33 | 34 | Add sub-middleware and template tags 35 | 36 | .. versionadded:: 0.3 37 | 38 | Add ordinal time field and change field options **use_float** to **use_numeric**!!! 39 | 40 | .. versionadded:: 0.1 41 | 42 | Initial 43 | 44 | 45 | Contents 46 | -------- 47 | 48 | Classes: 49 | 50 | * :class:`TimestampPatchMixin` 51 | * :class:`UnixTimeStampField` 52 | * :class:`OrdinalPatchMixin` 53 | * :class:`OrdinalField` 54 | 55 | Members 56 | ------- 57 | 58 | """ 59 | from __future__ import unicode_literals 60 | 61 | import datetime 62 | 63 | from django.db.models import Field 64 | from django.utils import timezone 65 | from django.core import exceptions 66 | from django.conf import settings 67 | from django.forms import fields 68 | 69 | import six 70 | 71 | from .submiddleware import field_value_middleware 72 | 73 | 74 | class TimestampPatchMixin(object): 75 | 76 | INT32 = (1 << 31) - 1 77 | MAX_TS, MIN_TS = 253402271999.999, -719162 # 9999/12/31 23:59:59, 1/1/1 00:00:00 78 | 79 | def _datetime_to_timestamp(self, v): 80 | """ 81 | Py2 doesn't supports timestamp() 82 | """ 83 | 84 | # stole from https://docs.python.org/3/library/datetime.html#datetime.datetime.timestamp 85 | if timezone.is_aware(v): 86 | return (v - timezone.datetime(1970, 1, 1, tzinfo=timezone.utc)).total_seconds() 87 | else: 88 | return (v - timezone.datetime(1970, 1, 1)).total_seconds() 89 | 90 | def get_datetimenow(self): 91 | """ 92 | get datetime now according to USE_TZ and default time 93 | """ 94 | value = timezone.datetime.utcnow() 95 | if settings.USE_TZ: 96 | value = timezone.localtime( 97 | timezone.make_aware(value, timezone.utc), 98 | timezone.get_default_timezone() 99 | ) 100 | return value 101 | 102 | def get_timestampnow(self): 103 | """ 104 | get utc unix timestamp 105 | """ 106 | return self._datetime_to_timestamp(timezone.datetime.utcnow()) 107 | 108 | def to_timestamp(self, value): 109 | """ 110 | from value to timestamp format(float) 111 | """ 112 | if isinstance(value, (six.integer_types, float, six.string_types)): 113 | try: 114 | return float(value) 115 | except ValueError: 116 | value = self.datetime_str_to_datetime(value) 117 | 118 | if isinstance(value, datetime.datetime): 119 | return self._datetime_to_timestamp(value) 120 | 121 | if value is None: 122 | try: 123 | return float(self.default) 124 | except: 125 | return 0.0 126 | 127 | raise exceptions.ValidationError( 128 | "Unable to convert value: '%s' to timestamp" % value, 129 | code="invalid_timestamp" 130 | ) 131 | 132 | def to_naive_datetime(self, value): 133 | """ 134 | from value to datetime with tzinfo format (datetime.datetime instance) 135 | """ 136 | if isinstance(value, (six.integer_types, float, six.string_types)): 137 | try: 138 | return self.from_number(value) 139 | except ValueError: 140 | return self.datetime_str_to_datetime(value) 141 | 142 | if isinstance(value, datetime.datetime): 143 | return value 144 | 145 | if value is None: 146 | try: 147 | return self.from_number(self.default) 148 | except: 149 | return timezone.datetime(1970, 1, 1, 0, 0) 150 | 151 | raise exceptions.ValidationError( 152 | "Unable to convert value: '%s' to python data type" % value, 153 | code="invalid_datetime" 154 | ) 155 | 156 | def to_utc_datetime(self, value): 157 | """ 158 | from value to datetime with tzinfo format (datetime.datetime instance) 159 | """ 160 | value = self.to_naive_datetime(value) 161 | 162 | if timezone.is_naive(value): 163 | value = timezone.make_aware(value, timezone.utc) 164 | else: 165 | value = timezone.localtime(value, timezone.utc) 166 | return value 167 | 168 | def to_default_timezone_datetime(self, value): 169 | """ 170 | convert to default timezone datetime 171 | """ 172 | return timezone.localtime(self.to_utc_datetime(value), timezone.get_default_timezone()) 173 | 174 | def to_datetime(self, value): 175 | 176 | if settings.USE_TZ: 177 | if settings.TIME_ZONE != 'UTC': 178 | return self.to_default_timezone_datetime(value) 179 | else: 180 | return self.to_utc_datetime(value) 181 | else: 182 | return self.to_naive_datetime(value) 183 | 184 | def datetime_str_to_datetime(self, value): 185 | try: 186 | if value.find('.') >= 0: 187 | return timezone.datetime.strptime(value, '%Y-%m-%d %H:%M:%S.%f') 188 | else: 189 | return timezone.datetime.strptime(value, '%Y-%m-%d %H:%M:%S') 190 | except: 191 | raise exceptions.ValidationError( 192 | "Unable to convert value: '%s' to datetime, " 193 | "please use 'YYYY-mm-dd HH:MM:SS'" % value, 194 | code="invalid_timestamp" 195 | ) 196 | 197 | def from_number(self, value): 198 | value = float(value) 199 | if value > self.MAX_TS or value < self.MIN_TS: 200 | raise exceptions.ValidationError( 201 | "Value out of range,acceptable: " 202 | "%s ~ %s (1/1/1 00:00:00 ~ 9999/12/31 23:59:59)" % (self.MIN_TS, self.MAX_TS), 203 | code="out_of_rnage" 204 | ) 205 | 206 | return timezone.datetime(1970, 1, 1, 0, 0) + timezone.timedelta(seconds=value) 207 | 208 | 209 | class UnixTimeStampField(TimestampPatchMixin, Field): 210 | """ 211 | Copy and mimic django.db.models.fields.DatetimeField 212 | Stored as float in database and used as datetime object in Python 213 | 214 | """ 215 | empty_strings_allowed = False 216 | description = "Unix POSIX timestamp" 217 | 218 | def __init__(self, verbose_name=None, name=None, auto_now=False, 219 | auto_now_add=False, round_to=6, use_numeric=False, **kwargs): 220 | self.auto_now, self.auto_now_add = auto_now, auto_now_add 221 | self.round_to, self.use_numeric = round_to, use_numeric 222 | if auto_now or auto_now_add: 223 | kwargs['editable'] = False 224 | kwargs['blank'] = True 225 | super(UnixTimeStampField, self).__init__(verbose_name, name, **kwargs) 226 | 227 | def deconstruct(self): 228 | name, path, args, kwargs = super(UnixTimeStampField, self).deconstruct() 229 | if self.auto_now: 230 | kwargs['auto_now'] = True 231 | if self.auto_now_add: 232 | kwargs['auto_now_add'] = True 233 | if self.auto_now or self.auto_now_add: 234 | del kwargs['editable'] 235 | del kwargs['blank'] 236 | return name, path, args, kwargs 237 | 238 | def get_internal_type(self): 239 | return "FloatField" 240 | 241 | def pre_save(self, model_instance, add): 242 | if self.auto_now or (self.auto_now_add and add): 243 | value = self.get_datetimenow() 244 | else: 245 | value = getattr(model_instance, self.attname) 246 | 247 | setattr(model_instance, self.attname, field_value_middleware(self, value)) 248 | return value 249 | 250 | def to_python(self, value): 251 | return field_value_middleware(self, value) 252 | 253 | def get_default(self): 254 | if self.auto_now or self.auto_now_add: 255 | v = self.get_datetimenow() 256 | else: 257 | v = 0.0 258 | 259 | if self.has_default(): 260 | v = self.default 261 | if callable(self.default): 262 | v = self.default() 263 | return self.to_python(v) 264 | 265 | def value_to_string(self, obj): 266 | val = self._get_val_from_obj(obj) 267 | return '' if val is None else val 268 | 269 | def get_prep_value(self, value): 270 | value = super(UnixTimeStampField, self).get_prep_value(value) 271 | return self.to_timestamp(value) 272 | 273 | def get_db_prep_value(self, value, connection, prepared=False): 274 | if not prepared: 275 | value = self.get_prep_value(value) 276 | return self.to_timestamp(value) 277 | 278 | def from_db_value(self, value, expression, connection): 279 | return field_value_middleware(self, value) 280 | 281 | def to_timestamp(self, value): 282 | return round(super(UnixTimeStampField, self).to_timestamp(value), self.round_to) 283 | 284 | def formfield(self, **kwargs): 285 | defaults = {'form_class': fields.CharField} 286 | defaults.update(kwargs) 287 | return super(UnixTimeStampField, self).formfield(**defaults) 288 | 289 | 290 | class OrdinalPatchMixin(TimestampPatchMixin): 291 | 292 | MAX_OD = 3652059 # 9999/12/31 293 | 294 | def _datetime_to_timestamp(self, v): 295 | """ 296 | overwrite to use toordinal 297 | """ 298 | return v.toordinal() 299 | 300 | def get_datetimenow(self): 301 | """ 302 | get datetime now according to USE_TZ and default time 303 | """ 304 | value = timezone.datetime.fromordinal(timezone.datetime.utcnow().toordinal()) 305 | if settings.USE_TZ: 306 | value = timezone.localtime( 307 | timezone.make_aware(value, timezone.utc), 308 | timezone.get_default_timezone() 309 | ) 310 | return value 311 | 312 | def to_timestamp(self, value): 313 | """ 314 | from value to ordinal timestamp format(int) 315 | """ 316 | if isinstance(value, (six.integer_types, float, six.string_types)): 317 | try: 318 | return int(value) 319 | except ValueError: 320 | value = self.datetime_str_to_datetime(value) 321 | 322 | if isinstance(value, datetime.datetime): 323 | if timezone.is_aware(value): 324 | value = timezone.localtime(value, timezone.utc) 325 | return self._datetime_to_timestamp(value) 326 | 327 | raise exceptions.ValidationError( 328 | "Unable to convert value: '%s' to timestamp" % value, 329 | code="invalid_timestamp" 330 | ) 331 | 332 | def to_naive_datetime(self, value): 333 | """ 334 | from value to datetime with tzinfo format (datetime.datetime instance) 335 | """ 336 | if isinstance(value, (six.integer_types, float, six.string_types)): 337 | try: 338 | return self.from_number(value) 339 | except ValueError: 340 | return self.datetime_str_to_datetime(value) 341 | 342 | if isinstance(value, datetime.datetime): 343 | return value 344 | 345 | raise exceptions.ValidationError( 346 | "Unable to convert value: '%s' to python data type" % value, 347 | code="invalid_datetime" 348 | ) 349 | 350 | def to_utc_datetime(self, value): 351 | """ 352 | from value to datetime with tzinfo format (datetime.datetime instance) 353 | """ 354 | if isinstance(value, (six.integer_types, float, six.string_types)): 355 | value = self.to_naive_datetime(value) 356 | 357 | if isinstance(value, datetime.datetime): 358 | if timezone.is_naive(value): 359 | value = timezone.make_aware(value, timezone.utc) 360 | else: 361 | value = timezone.localtime(value, timezone.utc) 362 | return value 363 | 364 | raise exceptions.ValidationError( 365 | "Unable to convert value: '%s' to python data type" % value, 366 | code="invalid_datetime" 367 | ) 368 | 369 | def from_number(self, value): 370 | value = int(value) 371 | if value > self.MAX_OD or value < 1: 372 | raise exceptions.ValidationError( 373 | "Value out of range, acceptable: 1 ~ %s (1/1/1 ~ 9999/12/31)" % self.MAX_OD, 374 | code="out_of_rnage" 375 | ) 376 | return timezone.datetime(1, 1, 1, 0, 0) + timezone.timedelta(days=(value-1)) 377 | 378 | 379 | class OrdinalField(OrdinalPatchMixin, UnixTimeStampField): 380 | """ 381 | Copy and mimic django.db.models.fields.DatetimeField 382 | Stored as float in database and used as datetime object in Python 383 | 384 | """ 385 | empty_strings_allowed = False 386 | description = "Ordinal timestamp" 387 | 388 | def __init__(self, verbose_name=None, name=None, auto_now=False, 389 | auto_now_add=False, use_numeric=False, **kwargs): 390 | self.auto_now, self.auto_now_add, self.use_numeric = auto_now, auto_now_add, use_numeric 391 | if auto_now or auto_now_add: 392 | kwargs['editable'] = False 393 | kwargs['blank'] = True 394 | super(UnixTimeStampField, self).__init__(verbose_name, name, **kwargs) 395 | 396 | def get_internal_type(self): 397 | return "FloatField" 398 | 399 | def formfield(self, **kwargs): 400 | defaults = {'form_class': fields.CharField} 401 | defaults.update(kwargs) 402 | return super(OrdinalField, self).formfield(**defaults) 403 | -------------------------------------------------------------------------------- /unixtimestampfield/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myyang/django-unixtimestampfield/f33a8aea42f477a8462331e6b62f121428a93447/unixtimestampfield/models.py -------------------------------------------------------------------------------- /unixtimestampfield/submiddleware.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Sub middleware 5 | 6 | release |release|, version |version| 7 | 8 | .. versionadded:: 0.3.8 9 | 10 | Bugs fixed: Apply submiddleware to auto_now field and check format in submiddleware 11 | 12 | .. versionadded:: 0.3.4 13 | 14 | Add extra function param 15 | 16 | .. versionadded:: 0.3.3 17 | 18 | Initial 19 | 20 | 21 | Contents 22 | -------- 23 | 24 | Functions: 25 | 26 | * :func:`field_value_middleware` 27 | 28 | Variables: 29 | 30 | * :data:`USF_FORMAT` 31 | * :data:`USF_DATETIME` 32 | * :data:`USF_TIMESTAMP` 33 | * :data:`USF_DEFAULT` 34 | 35 | Members 36 | ------- 37 | 38 | """ 39 | from django.conf import settings 40 | 41 | USF_DATETIME, USF_TIMESTAMP, USF_DEFAULT = 'usf_datetime', 'usf_timestamp', 'usf_default' 42 | 43 | 44 | def get_format(): 45 | usf_format = getattr(settings, 'USF_FORMAT', USF_DEFAULT) 46 | if usf_format not in [USF_DATETIME, USF_TIMESTAMP, USF_DEFAULT]: 47 | usf_format = USF_DEFAULT 48 | return usf_format 49 | 50 | 51 | USF_FORMAT = get_format() 52 | 53 | 54 | def field_value_middleware(field, value, usf_format=None): 55 | 56 | if usf_format is None: 57 | usf_format = get_format() 58 | 59 | if usf_format == USF_DEFAULT: 60 | return field.to_timestamp(value) if field.use_numeric else field.to_datetime(value) 61 | 62 | if usf_format == USF_DATETIME: 63 | return field.to_datetime(value) 64 | 65 | if usf_format == USF_TIMESTAMP: 66 | return field.to_timestamp(value) 67 | 68 | raise ValueError('USF_FORMAT: %s should not in optional values') 69 | -------------------------------------------------------------------------------- /unixtimestampfield/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myyang/django-unixtimestampfield/f33a8aea42f477a8462331e6b62f121428a93447/unixtimestampfield/templatetags/__init__.py -------------------------------------------------------------------------------- /unixtimestampfield/templatetags/unixtimestampfield.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | UnixTimeStampField template tags 4 | 5 | release |release|, version |version| 6 | 7 | 8 | .. versionadded:: 0.3.4 9 | 10 | Add extra function param 11 | 12 | .. versionadded:: 0.3.3 13 | 14 | Initial 15 | 16 | 17 | Contents 18 | -------- 19 | 20 | Functions: 21 | 22 | * :func:`to_datetime` 23 | * :func:`to_timestamp` 24 | 25 | Members 26 | ------- 27 | 28 | """ 29 | import time 30 | 31 | from django.template import Library 32 | from django.utils import timezone 33 | from django.conf import settings 34 | 35 | register = Library() 36 | 37 | 38 | @register.filter('to_datetime') 39 | def to_datetime(field): 40 | try: 41 | if type(field) in (float, str): 42 | field = timezone.datetime.fromtimestamp(field, timezone.utc) 43 | if settings.USE_TZ: 44 | field = timezone.localtime(field, timezone.get_default_timezone()) 45 | return field 46 | except: 47 | return "" 48 | 49 | 50 | @register.filter('to_timestamp') 51 | def to_timestamp(field): 52 | try: 53 | if type(field) == timezone.datetime: 54 | if settings.USE_TZ and timezone.is_aware(field): 55 | field = timezone.localtime(field, timezone.utc) 56 | # Py2 doesn't supports timestamp() 57 | if hasattr(field, 'timestamp'): 58 | return field.timestamp() 59 | return time.mktime(field.timetuple()) + field.microsecond * 0.00001 60 | 61 | return field 62 | except: 63 | return "" 64 | -------------------------------------------------------------------------------- /unixtimestampfield/tests.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from django.test import TestCase, override_settings 4 | 5 | from django.db import models 6 | from django.utils import timezone 7 | from django import forms 8 | from django.core import exceptions 9 | from django.template import Template, Context 10 | 11 | from .fields import UnixTimeStampField, OrdinalField, TimestampPatchMixin, OrdinalPatchMixin 12 | 13 | unix_0 = timezone.datetime(1970, 1, 1) 14 | unix_0_utc = timezone.datetime(1970, 1, 1, tzinfo=timezone.utc) 15 | 16 | ordinal_1 = timezone.datetime.fromordinal(1) 17 | ordinal_1_utc = timezone.make_aware(timezone.datetime.fromordinal(1), timezone.utc) 18 | 19 | logging.basicConfig() 20 | LOGGER = logging.getLogger(__name__) 21 | LOGGER.setLevel(logging.DEBUG) 22 | 23 | 24 | class MixinTest(TestCase): 25 | 26 | zero_utc = timezone.datetime(1970, 1, 1, 0, 0, tzinfo=timezone.utc) 27 | oneyear_utc = timezone.datetime( 28 | 1971, 1, 1, 1, 1, 1, 123400, tzinfo=timezone.utc) # 31539661.123400 29 | oneyear_utc_i = timezone.datetime(1971, 1, 1, 1, 1, 1, tzinfo=timezone.utc) # 31539661.0 30 | zero = timezone.datetime(1970, 1, 1, 0, 0) 31 | oneyear = timezone.datetime(1971, 1, 1, 1, 1, 1, 123400) 32 | oneyear_i = timezone.datetime(1971, 1, 1, 1, 1, 1) 33 | negyear_utc = timezone.datetime( 34 | 1969, 1, 1, 1, 1, 1, 123400, tzinfo=timezone.utc) # -31532338.8766 35 | negyear_utc_i = timezone.datetime(1969, 1, 1, 1, 1, 1, tzinfo=timezone.utc) # -31532339 36 | 37 | @override_settings(USE_TZ=True, TIME_ZONE='UTC') 38 | def test_to_timestamp_utc(self): 39 | ts = TimestampPatchMixin() 40 | 41 | self.assertEqual(0, ts.to_timestamp(self.zero_utc)) 42 | self.assertEqual(31539661.123400, ts.to_timestamp(self.oneyear_utc)) 43 | self.assertEqual(31539661, ts.to_timestamp(self.oneyear_utc_i)) 44 | self.assertEqual(-31532338.8766, ts.to_timestamp(self.negyear_utc)) 45 | self.assertEqual(-31532339, ts.to_timestamp(self.negyear_utc_i)) 46 | 47 | @override_settings(USE_TZ=True, TIME_ZONE='Asia/Taipei') 48 | def test_to_timestamp_with_tz(self): 49 | ts = TimestampPatchMixin() 50 | 51 | self.assertEqual(0, ts.to_timestamp(timezone.localtime(self.zero_utc))) 52 | self.assertEqual(31539661.123400, ts.to_timestamp(timezone.localtime(self.oneyear_utc))) 53 | self.assertEqual(31539661, ts.to_timestamp(timezone.localtime(self.oneyear_utc_i))) 54 | self.assertEqual(-31532338.8766, ts.to_timestamp(timezone.localtime(self.negyear_utc))) 55 | self.assertEqual(-31532339, ts.to_timestamp(timezone.localtime(self.negyear_utc_i))) 56 | 57 | @override_settings(USE_TZ=False) 58 | def test_to_timestamp_without_tz(self): 59 | ts = TimestampPatchMixin() 60 | 61 | self.assertEqual(0, ts.to_timestamp(self.zero_utc)) 62 | self.assertEqual(0, ts.to_timestamp(self.zero)) 63 | self.assertEqual(0, ts.to_timestamp(timezone.localtime(self.zero_utc))) 64 | self.assertEqual(31539661.123400, ts.to_timestamp(self.oneyear)) 65 | self.assertEqual(31539661.123400, ts.to_timestamp(self.oneyear_utc)) 66 | self.assertEqual(31539661, ts.to_timestamp(self.oneyear_utc_i)) 67 | self.assertEqual(-31532338.8766, ts.to_timestamp(self.negyear_utc)) 68 | self.assertEqual(-31532339, ts.to_timestamp(self.negyear_utc_i)) 69 | 70 | @override_settings(USE_TZ=True, TIME_ZONE='UTC') 71 | def test_to_naive_utc(self): 72 | ts = TimestampPatchMixin() 73 | 74 | self.assertEqual(self.zero, ts.to_naive_datetime(0)) 75 | self.assertEqual(self.zero, ts.to_naive_datetime(0.0)) 76 | self.assertEqual(self.zero, ts.to_naive_datetime('0')) 77 | self.assertEqual(self.zero, ts.to_naive_datetime('1970-01-01 00:00:00')) 78 | 79 | self.assertEqual(self.oneyear_i, ts.to_naive_datetime(31539661)) 80 | self.assertEqual(self.oneyear, ts.to_naive_datetime(31539661.123400)) 81 | self.assertEqual(self.oneyear, ts.to_naive_datetime('31539661.123400')) 82 | self.assertEqual(self.oneyear, ts.to_naive_datetime('1971-01-01 01:01:01.123400')) 83 | 84 | @override_settings(USE_TZ=True, TIME_ZONE='Asia/Taipei') 85 | def test_to_naive_with_tz(self): 86 | ts = TimestampPatchMixin() 87 | 88 | self.assertEqual(self.zero, ts.to_naive_datetime(0)) 89 | self.assertEqual(self.zero, ts.to_naive_datetime(0.0)) 90 | self.assertEqual(self.zero, ts.to_naive_datetime('0')) 91 | self.assertEqual(self.zero, ts.to_naive_datetime('1970-01-01 00:00:00')) 92 | 93 | self.assertEqual(self.oneyear_i, ts.to_naive_datetime(31539661)) 94 | self.assertEqual(self.oneyear, ts.to_naive_datetime(31539661.123400)) 95 | self.assertEqual(self.oneyear, ts.to_naive_datetime('31539661.123400')) 96 | self.assertEqual(self.oneyear, ts.to_naive_datetime('1971-01-01 01:01:01.123400')) 97 | 98 | @override_settings(USE_TZ=False) 99 | def test_to_naive_without_tz(self): 100 | ts = TimestampPatchMixin() 101 | 102 | self.assertEqual(self.zero, ts.to_naive_datetime(0)) 103 | self.assertEqual(self.zero, ts.to_naive_datetime(0.0)) 104 | self.assertEqual(self.zero, ts.to_naive_datetime('0')) 105 | self.assertEqual(self.zero, ts.to_naive_datetime('1970-01-01 00:00:00')) 106 | 107 | self.assertEqual(self.oneyear_i, ts.to_naive_datetime(31539661)) 108 | self.assertEqual(self.oneyear, ts.to_naive_datetime(31539661.123400)) 109 | self.assertEqual(self.oneyear, ts.to_naive_datetime('31539661.123400')) 110 | self.assertEqual(self.oneyear, ts.to_naive_datetime('1971-01-01 01:01:01.123400')) 111 | 112 | @override_settings(USE_TZ=True, TIME_ZONE='UTC') 113 | def test_to_utc_utc(self): 114 | ts = TimestampPatchMixin() 115 | 116 | self.assertEqual(self.zero_utc, ts.to_utc_datetime(0)) 117 | self.assertEqual(self.zero_utc, ts.to_utc_datetime(0.0)) 118 | self.assertEqual(self.zero_utc, ts.to_utc_datetime('0')) 119 | self.assertEqual(self.zero_utc, ts.to_utc_datetime('1970-01-01 00:00:00')) 120 | 121 | self.assertEqual(self.oneyear_utc_i, ts.to_utc_datetime(31539661)) 122 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime(31539661.123400)) 123 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime('31539661.123400')) 124 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime('1971-01-01 01:01:01.123400')) 125 | 126 | @override_settings(USE_TZ=True, TIME_ZONE='Asia/Taipei') 127 | def test_to_utc_with_tz(self): 128 | ts = TimestampPatchMixin() 129 | 130 | self.assertEqual(self.zero_utc, ts.to_utc_datetime(0)) 131 | self.assertEqual(self.zero_utc, ts.to_utc_datetime(0.0)) 132 | self.assertEqual(self.zero_utc, ts.to_utc_datetime('0')) 133 | self.assertEqual(self.zero_utc, ts.to_utc_datetime('1970-01-01 00:00:00')) 134 | 135 | self.assertEqual(self.oneyear_utc_i, ts.to_utc_datetime(31539661)) 136 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime(31539661.123400)) 137 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime('31539661.123400')) 138 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime('1971-01-01 01:01:01.123400')) 139 | 140 | @override_settings(USE_TZ=False) 141 | def test_to_utc_without_tz(self): 142 | ts = TimestampPatchMixin() 143 | 144 | self.assertEqual(self.zero_utc, ts.to_utc_datetime(0)) 145 | self.assertEqual(self.zero_utc, ts.to_utc_datetime(0.0)) 146 | self.assertEqual(self.zero_utc, ts.to_utc_datetime('0')) 147 | self.assertEqual(self.zero_utc, ts.to_utc_datetime('1970-01-01 00:00:00')) 148 | 149 | self.assertEqual(self.oneyear_utc_i, ts.to_utc_datetime(31539661)) 150 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime(31539661.123400)) 151 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime('31539661.123400')) 152 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime('1971-01-01 01:01:01.123400')) 153 | 154 | @override_settings(USE_TZ=True, TIME_ZONE='UTC') 155 | def test_to_datetime_utc(self): 156 | ts = TimestampPatchMixin() 157 | 158 | self.assertEqual(self.zero_utc, ts.to_datetime(0)) 159 | self.assertEqual(self.zero_utc, ts.to_datetime(0.0)) 160 | self.assertEqual(self.zero_utc, ts.to_datetime('0')) 161 | self.assertEqual(self.zero_utc, ts.to_datetime('1970-01-01 00:00:00')) 162 | 163 | self.assertEqual(self.oneyear_utc_i, ts.to_datetime(31539661)) 164 | self.assertEqual(self.oneyear_utc, ts.to_datetime(31539661.123400)) 165 | self.assertEqual(self.oneyear_utc, ts.to_datetime('31539661.123400')) 166 | self.assertEqual(self.oneyear_utc, ts.to_datetime('1971-01-01 01:01:01.123400')) 167 | 168 | @override_settings(USE_TZ=True, TIME_ZONE='Asia/Taipei') 169 | def test_to_datetime_with_tz(self): 170 | ts = TimestampPatchMixin() 171 | zero = timezone.localtime(self.zero_utc) 172 | oneyear = timezone.localtime(self.oneyear_utc) 173 | oneyear_i = timezone.localtime(self.oneyear_utc_i) 174 | 175 | self.assertEqual(zero, ts.to_datetime(0)) 176 | self.assertEqual(zero, ts.to_datetime(0.0)) 177 | self.assertEqual(zero, ts.to_datetime('0')) 178 | self.assertEqual(zero, ts.to_datetime('1970-01-01 00:00:00')) 179 | 180 | self.assertEqual(oneyear_i, ts.to_datetime(31539661)) 181 | self.assertEqual(oneyear, ts.to_datetime(31539661.123400)) 182 | self.assertEqual(oneyear, ts.to_datetime('31539661.123400')) 183 | self.assertEqual(oneyear, ts.to_datetime('1971-01-01 01:01:01.123400')) 184 | 185 | @override_settings(USE_TZ=False) 186 | def test_to_datetime_without_tz(self): 187 | ts = TimestampPatchMixin() 188 | 189 | self.assertEqual(self.zero, ts.to_datetime(0)) 190 | self.assertEqual(self.zero, ts.to_datetime(0.0)) 191 | self.assertEqual(self.zero, ts.to_datetime('0')) 192 | self.assertEqual(self.zero, ts.to_datetime('1970-01-01 00:00:00')) 193 | 194 | self.assertEqual(self.oneyear_i, ts.to_datetime(31539661)) 195 | self.assertEqual(self.oneyear, ts.to_datetime(31539661.123400)) 196 | self.assertEqual(self.oneyear, ts.to_datetime('31539661.123400')) 197 | self.assertEqual(self.oneyear, ts.to_datetime('1971-01-01 01:01:01.123400')) 198 | 199 | @override_settings(USE_TZ=True, TIME_ZONE='UTC') 200 | def test_over_and_under_flow(self): 201 | ts = TimestampPatchMixin() 202 | 203 | self.assertRaises(exceptions.ValidationError, ts.from_number, 253402272000) 204 | self.assertRaises(exceptions.ValidationError, ts.from_number, -719163) 205 | 206 | 207 | class ForTestModel(models.Model): 208 | 209 | created = UnixTimeStampField(auto_now_add=True) 210 | modified = UnixTimeStampField(auto_now=True) 211 | str_ini = UnixTimeStampField(default='0.0') 212 | str_dt_ini = UnixTimeStampField(default='1970-01-01 00:00:00') 213 | float_ini = UnixTimeStampField(default=0.0) 214 | int_ini = UnixTimeStampField(default=0.0) 215 | dt_ini = UnixTimeStampField(default=unix_0_utc) 216 | 217 | use_numeric_field = UnixTimeStampField(use_numeric=True, default=0.0) 218 | round_3_field = UnixTimeStampField(use_numeric=True, round_to=3, default=0.0) 219 | 220 | 221 | class TimeStampFieldTest(TestCase): 222 | 223 | @override_settings(USE_TZ=True, TIME_ZONE='UTC') 224 | def test_init_with_use_tz(self): 225 | now = timezone.now() 226 | expected = timezone.datetime(1970, 1, 1, tzinfo=timezone.utc) 227 | t = ForTestModel.objects.create() 228 | 229 | self.assertGreater(t.created, now) 230 | self.assertGreater(t.modified, now) 231 | self.assertEqual(t.str_ini, expected) 232 | self.assertEqual(t.str_dt_ini, expected) 233 | self.assertEqual(t.float_ini, expected) 234 | self.assertEqual(t.int_ini, expected) 235 | 236 | @override_settings(USE_TZ=True, TIME_ZONE='UTC') 237 | def test_assignment_with_tz(self): 238 | expected = timezone.datetime(1970, 1, 1, 0, 0, 3, tzinfo=timezone.utc) 239 | t = ForTestModel.objects.create() 240 | 241 | pre_modified = t.modified 242 | 243 | t.str_ini = '3' 244 | t.str_dt_ini = '1970-01-01 00:00:03' 245 | t.float_ini = 3.0 246 | t.int_ini = 3 247 | t.dt_ini = timezone.datetime(1970, 1, 1, 0, 0, 3, tzinfo=timezone.utc) 248 | t.use_numeric_field = 3.1111116 249 | t.round_3_field = 3.1116 250 | t.save() 251 | 252 | if hasattr(t, 'refresh_from_db'): 253 | t.refresh_from_db() 254 | else: 255 | t = ForTestModel.objects.get(id=t.id) 256 | 257 | self.assertGreater(t.modified, pre_modified) 258 | self.assertEqual(t.str_ini, expected) 259 | self.assertEqual(t.str_dt_ini, expected) 260 | self.assertEqual(t.float_ini, expected) 261 | self.assertEqual(t.int_ini, expected) 262 | self.assertEqual(t.use_numeric_field, 3.111112) 263 | self.assertEqual(t.round_3_field, 3.112) 264 | 265 | @override_settings(USE_TZ=True, TIME_ZONE='Asia/Taipei') 266 | def test_init_with_different_tz(self): 267 | now = timezone.now() 268 | expected = timezone.localtime( 269 | timezone.datetime(1970, 1, 1, tzinfo=timezone.utc), 270 | timezone.pytz.timezone('Asia/Taipei') 271 | ) 272 | t = ForTestModel.objects.create() 273 | 274 | self.assertGreater(t.created, now) 275 | self.assertGreater(t.modified, now) 276 | self.assertEqual(t.str_ini, expected) 277 | self.assertEqual(t.str_dt_ini, expected) 278 | self.assertEqual(t.float_ini, expected) 279 | self.assertEqual(t.int_ini, expected) 280 | 281 | @override_settings(USE_TZ=True, TIME_ZONE='Asia/Taipei') 282 | def test_assignment_with_different_tz(self): 283 | expected = timezone.localtime( 284 | timezone.datetime(1970, 1, 1, 0, 0, 3, tzinfo=timezone.utc), 285 | timezone.pytz.timezone('Asia/Taipei') 286 | ) 287 | 288 | t = ForTestModel.objects.create() 289 | 290 | pre_modified = t.modified 291 | 292 | t.str_ini = '3' 293 | t.str_dt_ini = '1970-01-01 00:00:03' 294 | t.float_ini = 3.0 295 | t.int_ini = 3 296 | t.dt_ini = timezone.datetime.fromtimestamp(3.0, timezone.pytz.timezone('Asia/Taipei')) 297 | t.use_numeric_field = 3.1111116 298 | t.round_3_field = 3.1116 299 | t.save() 300 | 301 | if hasattr(t, 'refresh_from_db'): 302 | t.refresh_from_db() 303 | else: 304 | t = ForTestModel.objects.get(id=t.id) 305 | 306 | self.assertGreater(t.modified, pre_modified) 307 | self.assertEqual(t.str_ini, expected) 308 | self.assertEqual(t.str_dt_ini, expected) 309 | self.assertEqual(t.float_ini, expected) 310 | self.assertEqual(t.int_ini, expected) 311 | self.assertEqual(t.use_numeric_field, 3.111112) 312 | self.assertEqual(t.round_3_field, 3.112) 313 | 314 | @override_settings(USE_TZ=False) 315 | def test_init_without_tz(self): 316 | now = timezone.datetime.utcnow() 317 | expected = timezone.datetime(1970, 1, 1, 0, 0) 318 | t = ForTestModel.objects.create() 319 | 320 | self.assertGreater(t.created, now) 321 | self.assertGreater(t.modified, now) 322 | self.assertEqual(t.str_ini, expected) 323 | self.assertEqual(t.str_dt_ini, expected) 324 | self.assertEqual(t.float_ini, expected) 325 | self.assertEqual(t.int_ini, expected) 326 | 327 | @override_settings(USE_TZ=False) 328 | def test_assignment_without_tz(self): 329 | expected = timezone.datetime(1970, 1, 1, 0, 0, 3) 330 | t = ForTestModel.objects.create() 331 | 332 | pre_modified = t.modified 333 | 334 | t.str_ini = '3' 335 | t.str_dt_ini = '1970-01-01 00:00:03' 336 | t.float_ini = 3.0 337 | t.int_ini = 3 338 | t.dt_ini = timezone.datetime.fromtimestamp(3.0) 339 | t.save() 340 | 341 | if hasattr(t, 'refresh_from_db'): 342 | t.refresh_from_db() 343 | else: 344 | t = ForTestModel.objects.get(id=t.id) 345 | 346 | self.assertGreater(t.modified, pre_modified) 347 | self.assertEqual(t.str_ini, expected) 348 | self.assertEqual(t.str_dt_ini, expected) 349 | self.assertEqual(t.float_ini, expected) 350 | self.assertEqual(t.int_ini, expected) 351 | 352 | @override_settings(USE_TZ=False) 353 | def test_assignment_with_big_num(self): 354 | expected = timezone.datetime(1970, 1, 1, 0, 0) + timezone.timedelta(seconds=14248491461) 355 | t = ForTestModel.objects.create() 356 | 357 | pre_modified = t.modified 358 | 359 | t.str_ini = '14248491461' 360 | t.float_ini = 14248491461.0 361 | t.int_ini = 14248491461 362 | t.dt_ini = timezone.datetime.fromtimestamp(14248491461.0) 363 | t.save() 364 | 365 | if hasattr(t, 'refresh_from_db'): 366 | t.refresh_from_db() 367 | else: 368 | t = ForTestModel.objects.get(id=t.id) 369 | 370 | self.assertGreater(t.modified, pre_modified) 371 | self.assertEqual(t.str_ini, expected) 372 | self.assertEqual(t.float_ini, expected) 373 | self.assertEqual(t.int_ini, expected) 374 | 375 | @override_settings(USE_TZ=False) 376 | def test_assignment_overflow(self): 377 | 378 | t = ForTestModel.objects.create() 379 | t.float_ini = 14248491461222.0 380 | 381 | self.assertRaises(exceptions.ValidationError, t.save) 382 | 383 | 384 | class ForTestModelForm(forms.ModelForm): 385 | 386 | class Meta: 387 | model = ForTestModel 388 | fields = ['str_ini', 'float_ini', 'int_ini', 'dt_ini', 389 | 'use_numeric_field', 'round_3_field'] 390 | 391 | 392 | class FormFieldTest(TestCase): 393 | 394 | def test_noraml(self): 395 | data = { 396 | 'str_ini': '1999-12-11 10:23:13', 397 | 'float_ini': 3.0, 398 | 'int_ini': 3, 399 | 'dt_ini': 3, 400 | 'use_numeric_field': 0, 401 | 'round_3_field': 0, 402 | } 403 | 404 | tform = ForTestModelForm(data=data) 405 | 406 | self.assertTrue(tform.is_valid()) 407 | 408 | def test_empty_form(self): 409 | 410 | data = {} 411 | 412 | tform = ForTestModelForm(data=data) 413 | 414 | self.assertFalse(tform.is_valid()) 415 | errors = {'dt_ini': [u'This field is required.'], 416 | 'float_ini': [u'This field is required.'], 417 | 'int_ini': [u'This field is required.'], 418 | 'round_3_field': [u'This field is required.'], 419 | 'str_ini': [u'This field is required.'], 420 | 'use_numeric_field': [u'This field is required.']} 421 | self.assertDictEqual(tform.errors, errors) 422 | self.assertEqual(tform.error_class, forms.utils.ErrorList) 423 | 424 | def test_partial_data(self): 425 | 426 | data = { 427 | 'int_ini': 0, 428 | 'round_3_field': 0, 429 | 'str_ini': '3', 430 | } 431 | 432 | tform = ForTestModelForm(data=data) 433 | 434 | self.assertFalse(tform.is_valid()) 435 | errors = {'dt_ini': [u'This field is required.'], 436 | 'float_ini': [u'This field is required.'], 437 | 'use_numeric_field': [u'This field is required.']} 438 | self.assertDictEqual(tform.errors, errors) 439 | self.assertEqual(tform.error_class, forms.utils.ErrorList) 440 | 441 | def test_invalid_data(self): 442 | 443 | data = { 444 | 'str_ini': ['hello'], 445 | 'float_ini': 3.0, 446 | 'int_ini': 3, 447 | 'dt_ini': 3, 448 | 'use_numeric_field': 0, 449 | 'round_3_field': 0, 450 | } 451 | 452 | tform = ForTestModelForm(data=data) 453 | 454 | self.assertFalse(tform.is_valid()) 455 | errors = {'str_ini': [u"Unable to convert value: '['hello']' to datetime" 456 | u", please use 'YYYY-mm-dd HH:MM:SS'"]} 457 | self.assertDictEqual(tform.errors, errors) 458 | self.assertEqual(tform.error_class, forms.utils.ErrorList) 459 | 460 | 461 | class OrdMixinTest(TestCase): 462 | 463 | zero_utc = timezone.datetime(1, 1, 1, 0, 0, tzinfo=timezone.utc) 464 | oneyear_utc = timezone.datetime(1, 12, 31, 0, 0, tzinfo=timezone.utc) # 365 465 | zero = timezone.datetime(1, 1, 1, 0, 0) 466 | oneyear = timezone.datetime(1, 12, 31, 0, 0) # 365 467 | 468 | @override_settings(USE_TZ=True, TIME_ZONE='UTC') 469 | def test_to_timestamp_utc(self): 470 | ts = OrdinalPatchMixin() 471 | 472 | self.assertEqual(1, ts.to_timestamp(self.zero_utc)) 473 | self.assertEqual(365, ts.to_timestamp(self.oneyear_utc)) 474 | 475 | @override_settings(USE_TZ=True, TIME_ZONE='Asia/Taipei') 476 | def test_to_timestamp_with_tz(self): 477 | ts = OrdinalPatchMixin() 478 | 479 | self.assertEqual(1, ts.to_timestamp(timezone.localtime(self.zero_utc))) 480 | self.assertEqual(365, ts.to_timestamp(timezone.localtime(self.oneyear_utc))) 481 | 482 | @override_settings(USE_TZ=False) 483 | def test_to_timestamp_without_tz(self): 484 | ts = OrdinalPatchMixin() 485 | 486 | self.assertEqual(1, ts.to_timestamp(self.zero_utc)) 487 | self.assertEqual(1, ts.to_timestamp(self.zero)) 488 | self.assertEqual(365, ts.to_timestamp(self.oneyear)) 489 | 490 | @override_settings(USE_TZ=True, TIME_ZONE='UTC') 491 | def test_to_naive_utc(self): 492 | ts = OrdinalPatchMixin() 493 | 494 | self.assertEqual(self.zero, ts.to_naive_datetime(1)) 495 | self.assertEqual(self.zero, ts.to_naive_datetime(1.0)) 496 | self.assertEqual(self.zero, ts.to_naive_datetime('1')) 497 | self.assertEqual(self.zero, ts.to_naive_datetime('0001-01-01 00:00:00')) 498 | 499 | self.assertEqual(self.oneyear, ts.to_naive_datetime(365)) 500 | self.assertEqual(self.oneyear, ts.to_naive_datetime(365.0)) 501 | self.assertEqual(self.oneyear, ts.to_naive_datetime('365')) 502 | self.assertEqual(self.oneyear, ts.to_naive_datetime('0001-12-31 00:00:00')) 503 | 504 | @override_settings(USE_TZ=True, TIME_ZONE='Asia/Taipei') 505 | def test_to_naive_with_tz(self): 506 | ts = OrdinalPatchMixin() 507 | 508 | self.assertEqual(self.zero, ts.to_naive_datetime(1)) 509 | self.assertEqual(self.zero, ts.to_naive_datetime(1.0)) 510 | self.assertEqual(self.zero, ts.to_naive_datetime('1')) 511 | self.assertEqual(self.zero, ts.to_naive_datetime('0001-01-01 00:00:00')) 512 | 513 | self.assertEqual(self.oneyear, ts.to_naive_datetime(365)) 514 | self.assertEqual(self.oneyear, ts.to_naive_datetime(365.0)) 515 | self.assertEqual(self.oneyear, ts.to_naive_datetime('365')) 516 | self.assertEqual(self.oneyear, ts.to_naive_datetime('0001-12-31 00:00:00')) 517 | 518 | @override_settings(USE_TZ=False) 519 | def test_to_naive_without_tz(self): 520 | ts = OrdinalPatchMixin() 521 | 522 | self.assertEqual(self.zero, ts.to_naive_datetime(1)) 523 | self.assertEqual(self.zero, ts.to_naive_datetime(1.0)) 524 | self.assertEqual(self.zero, ts.to_naive_datetime('1')) 525 | self.assertEqual(self.zero, ts.to_naive_datetime('0001-01-01 00:00:00')) 526 | 527 | self.assertEqual(self.oneyear, ts.to_naive_datetime(365)) 528 | self.assertEqual(self.oneyear, ts.to_naive_datetime(365.0)) 529 | self.assertEqual(self.oneyear, ts.to_naive_datetime('365')) 530 | self.assertEqual(self.oneyear, ts.to_naive_datetime('0001-12-31 00:00:00')) 531 | 532 | @override_settings(USE_TZ=True, TIME_ZONE='UTC') 533 | def test_to_utc_utc(self): 534 | ts = OrdinalPatchMixin() 535 | 536 | self.assertEqual(self.zero_utc, ts.to_utc_datetime(1)) 537 | self.assertEqual(self.zero_utc, ts.to_utc_datetime(1.0)) 538 | self.assertEqual(self.zero_utc, ts.to_utc_datetime('1')) 539 | self.assertEqual(self.zero_utc, ts.to_utc_datetime('0001-01-01 00:00:00')) 540 | 541 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime(365)) 542 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime(365.0)) 543 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime('365')) 544 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime('0001-12-31 00:00:00')) 545 | 546 | @override_settings(USE_TZ=True, TIME_ZONE='Asia/Taipei') 547 | def test_to_utc_with_tz(self): 548 | ts = OrdinalPatchMixin() 549 | 550 | self.assertEqual(self.zero_utc, ts.to_utc_datetime(1)) 551 | self.assertEqual(self.zero_utc, ts.to_utc_datetime(1.0)) 552 | self.assertEqual(self.zero_utc, ts.to_utc_datetime('1')) 553 | self.assertEqual(self.zero_utc, ts.to_utc_datetime('0001-01-01 00:00:00')) 554 | 555 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime(365)) 556 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime(365.0)) 557 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime('365')) 558 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime('0001-12-31 00:00:00')) 559 | 560 | @override_settings(USE_TZ=False) 561 | def test_to_utc_without_tz(self): 562 | ts = OrdinalPatchMixin() 563 | 564 | self.assertEqual(self.zero_utc, ts.to_utc_datetime(1)) 565 | self.assertEqual(self.zero_utc, ts.to_utc_datetime(1.0)) 566 | self.assertEqual(self.zero_utc, ts.to_utc_datetime('1')) 567 | self.assertEqual(self.zero_utc, ts.to_utc_datetime('0001-01-01 00:00:00')) 568 | 569 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime(365)) 570 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime(365.0)) 571 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime('365')) 572 | self.assertEqual(self.oneyear_utc, ts.to_utc_datetime('0001-12-31 00:00:00')) 573 | 574 | @override_settings(USE_TZ=True, TIME_ZONE='UTC') 575 | def test_to_datetime_utc(self): 576 | ts = OrdinalPatchMixin() 577 | 578 | self.assertEqual(self.zero_utc, ts.to_datetime(1)) 579 | self.assertEqual(self.zero_utc, ts.to_datetime(1.0)) 580 | self.assertEqual(self.zero_utc, ts.to_datetime('1')) 581 | self.assertEqual(self.zero_utc, ts.to_datetime('0001-01-01 00:00:00')) 582 | 583 | self.assertEqual(self.oneyear_utc, ts.to_datetime(365)) 584 | self.assertEqual(self.oneyear_utc, ts.to_datetime(365.0)) 585 | self.assertEqual(self.oneyear_utc, ts.to_datetime('365')) 586 | self.assertEqual(self.oneyear_utc, ts.to_datetime('0001-12-31 00:00:00')) 587 | 588 | @override_settings(USE_TZ=True, TIME_ZONE='Asia/Taipei') 589 | def test_to_datetime_with_tz(self): 590 | ts = OrdinalPatchMixin() 591 | zero = timezone.localtime(self.zero_utc) 592 | oneyear = timezone.localtime(self.oneyear_utc) 593 | 594 | self.assertEqual(zero, ts.to_datetime(1)) 595 | self.assertEqual(zero, ts.to_datetime(1.0)) 596 | self.assertEqual(zero, ts.to_datetime('1')) 597 | self.assertEqual(zero, ts.to_datetime('0001-01-01 00:00:00')) 598 | 599 | self.assertEqual(oneyear, ts.to_datetime(365)) 600 | self.assertEqual(oneyear, ts.to_datetime(365.0)) 601 | self.assertEqual(oneyear, ts.to_datetime('365')) 602 | self.assertEqual(oneyear, ts.to_datetime('0001-12-31 00:00:00')) 603 | 604 | @override_settings(USE_TZ=False) 605 | def test_to_datetime_without_tz(self): 606 | ts = OrdinalPatchMixin() 607 | 608 | self.assertEqual(self.zero, ts.to_datetime(1)) 609 | self.assertEqual(self.zero, ts.to_datetime(1.0)) 610 | self.assertEqual(self.zero, ts.to_datetime('1')) 611 | self.assertEqual(self.zero, ts.to_datetime('0001-01-01 00:00:00')) 612 | 613 | self.assertEqual(self.oneyear, ts.to_datetime(365)) 614 | self.assertEqual(self.oneyear, ts.to_datetime(365.0)) 615 | self.assertEqual(self.oneyear, ts.to_datetime('365')) 616 | self.assertEqual(self.oneyear, ts.to_datetime('0001-12-31 00:00:00')) 617 | 618 | @override_settings(USE_TZ=True, TIME_ZONE='UTC') 619 | def test_over_and_under_flow(self): 620 | ts = OrdinalPatchMixin() 621 | 622 | self.assertRaises(exceptions.ValidationError, ts.from_number, 3652060) 623 | self.assertRaises(exceptions.ValidationError, ts.from_number, 0) 624 | self.assertRaises(exceptions.ValidationError, ts.from_number, -1) 625 | 626 | 627 | class ForOrdinalTestModel(models.Model): 628 | 629 | created = OrdinalField(auto_now_add=True) 630 | modified = OrdinalField(auto_now=True) 631 | str_ini = OrdinalField(default='1') 632 | float_ini = OrdinalField(default=1) 633 | int_ini = OrdinalField(default=1) 634 | dt_ini = OrdinalField(default=ordinal_1) 635 | 636 | 637 | class OrdinalFieldTest(TestCase): 638 | 639 | @override_settings(USE_TZ=True, TIME_ZONE='UTC') 640 | def test_init_with_utc(self): 641 | today = timezone.make_aware( 642 | timezone.datetime.fromordinal(timezone.now().toordinal()), timezone.utc) 643 | expected = timezone.make_aware(timezone.datetime.fromordinal(1), timezone.utc) 644 | m = ForOrdinalTestModel.objects.create() 645 | 646 | self.assertEqual(m.created, today) 647 | self.assertEqual(m.modified, today) 648 | self.assertEqual(m.str_ini, expected) 649 | self.assertEqual(m.float_ini, expected) 650 | self.assertEqual(m.int_ini, expected) 651 | self.assertEqual(m.dt_ini, expected) 652 | 653 | @override_settings(USE_TZ=True, TIME_ZONE='UTC') 654 | def test_assignment_with_tz(self): 655 | today = timezone.make_aware( 656 | timezone.datetime.fromordinal(timezone.now().toordinal()), timezone.utc) 657 | expected = timezone.make_aware(timezone.datetime.fromordinal(3), timezone.utc) 658 | m = ForOrdinalTestModel.objects.create() 659 | 660 | m.str_ini = '3' 661 | m.float_ini = 3.0 662 | m.int_ini = 3 663 | m.dt_ini = timezone.make_aware(timezone.datetime.fromordinal(3), timezone.utc) 664 | m.save() 665 | 666 | if hasattr(m, 'refresh_from_db'): 667 | m.refresh_from_db() 668 | else: 669 | m = ForOrdinalTestModel.objects.get(id=m.id) 670 | 671 | self.assertEqual(m.modified, today) 672 | self.assertEqual(m.str_ini, expected) 673 | self.assertEqual(m.float_ini, expected) 674 | self.assertEqual(m.int_ini, expected) 675 | 676 | @override_settings(USE_TZ=True, TIME_ZONE='Asia/Taipei') 677 | def test_init_with_different_tz(self): 678 | today = timezone.make_aware( 679 | timezone.datetime.fromordinal(timezone.now().toordinal()), timezone.utc) 680 | expected = timezone.localtime( 681 | timezone.make_aware(timezone.datetime.fromordinal(1), timezone.utc), 682 | timezone.pytz.timezone('Asia/Taipei') 683 | ) 684 | m = ForOrdinalTestModel.objects.create() 685 | 686 | self.assertEqual(m.created, today) 687 | self.assertEqual(m.modified, today) 688 | self.assertEqual(m.str_ini, expected) 689 | self.assertEqual(m.float_ini, expected) 690 | self.assertEqual(m.int_ini, expected) 691 | 692 | @override_settings(USE_TZ=False) 693 | def test_init_without_tz(self): 694 | today = timezone.datetime.fromordinal(timezone.datetime.utcnow().toordinal()) 695 | expected = timezone.datetime.fromordinal(1) 696 | m = ForOrdinalTestModel.objects.create() 697 | 698 | self.assertEqual(m.created, today) 699 | self.assertEqual(m.modified, today) 700 | self.assertEqual(m.str_ini, expected) 701 | self.assertEqual(m.float_ini, expected) 702 | self.assertEqual(m.int_ini, expected) 703 | 704 | @override_settings(USE_TZ=False) 705 | def test_assignment_without_tz(self): 706 | 707 | today = timezone.datetime.fromordinal(timezone.datetime.utcnow().toordinal()) 708 | expected = timezone.datetime.fromordinal(3) 709 | m = ForOrdinalTestModel.objects.create() 710 | 711 | m.str_ini = '3' 712 | m.float_ini = 3.0 713 | m.int_ini = 3 714 | m.dt_ini = timezone.datetime.fromordinal(3) 715 | m.save() 716 | 717 | if hasattr(m, 'refresh_from_db'): 718 | m.refresh_from_db() 719 | else: 720 | m = ForOrdinalTestModel.objects.get(id=m.id) 721 | 722 | self.assertEqual(m.modified, today) 723 | self.assertEqual(m.str_ini, expected) 724 | self.assertEqual(m.float_ini, expected) 725 | self.assertEqual(m.int_ini, expected) 726 | 727 | @override_settings(USE_TZ=False) 728 | def test_assignment_overflow(self): 729 | 730 | t = ForOrdinalTestModel.objects.create() 731 | t.float_ini = 14248491461222.0 732 | 733 | self.assertRaises(exceptions.ValidationError, t.save) 734 | 735 | 736 | class TemplateTagsTest(TestCase): 737 | 738 | def setUp(self): 739 | self.template = Template( 740 | "{% load unixtimestampfield %} " 741 | "{{t.str_ini|to_datetime}} " 742 | "{{t.str_ini|to_timestamp}}" 743 | ) 744 | 745 | @override_settings(USE_TZ=True, TIME_ZONE='UTC') 746 | def test_render(self): 747 | t = ForTestModel() 748 | rendered = self.template.render(Context({'t': t})) 749 | self.assertIn("Jan. 1, 1970", rendered) 750 | self.assertIn("0.0", rendered) 751 | 752 | 753 | class SubmiddlewareModel(models.Model): 754 | 755 | datetime = UnixTimeStampField(default=0.0) 756 | numeric = UnixTimeStampField(use_numeric=True, default=0.0) 757 | 758 | 759 | class SubmiddlewareTest(TestCase): 760 | 761 | @override_settings(USE_TZ=True, TIME_ZONE='UTC') 762 | def test_default(self): 763 | t = SubmiddlewareModel.objects.create() 764 | expected = timezone.datetime(1970, 1, 1, 0, 0, 0, tzinfo=timezone.utc) 765 | 766 | if hasattr(t, 'refresh_from_db'): 767 | t.refresh_from_db() 768 | else: 769 | t = ForTestModel.objects.get(id=t.id) 770 | 771 | self.assertEqual(t.datetime, expected) 772 | self.assertEqual(t.numeric, 0) 773 | 774 | @override_settings(USE_TZ=True, TIME_ZONE='UTC', USF_FORMAT='usf_datetime') 775 | def test_datetime(self): 776 | t = SubmiddlewareModel.objects.create() 777 | expected = timezone.datetime(1970, 1, 1, 0, 0, 0, tzinfo=timezone.utc) 778 | 779 | self.assertEqual(t.datetime, expected) 780 | self.assertEqual(t.numeric, expected) 781 | 782 | @override_settings(USE_TZ=True, TIME_ZONE='UTC', USF_FORMAT='usf_timestamp') 783 | def test_timestamp(self): 784 | t = SubmiddlewareModel.objects.create() 785 | 786 | self.assertEqual(t.datetime, 0) 787 | self.assertEqual(t.numeric, 0) 788 | 789 | @override_settings(USE_TZ=True, TIME_ZONE='UTC', USF_FORMAT='invalid') 790 | def test_invalid_option(self): 791 | t = SubmiddlewareModel.objects.create() 792 | expected = timezone.datetime(1970, 1, 1, 0, 0, 0, tzinfo=timezone.utc) 793 | 794 | if hasattr(t, 'refresh_from_db'): 795 | t.refresh_from_db() 796 | else: 797 | t = ForTestModel.objects.get(id=t.id) 798 | 799 | self.assertEqual(t.datetime, expected) 800 | self.assertEqual(t.numeric, 0) 801 | -------------------------------------------------------------------------------- /unixtimestampfield/views.py: -------------------------------------------------------------------------------- 1 | #from django.shortcuts import render 2 | 3 | # Create your views here. 4 | --------------------------------------------------------------------------------