235 lines
6.5 KiB
Python
235 lines
6.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
import datetime as dt
|
|
|
|
import pytest
|
|
|
|
try:
|
|
import dateutil
|
|
except ImportError:
|
|
dateutil = None
|
|
|
|
from mongoengine import *
|
|
from mongoengine import connection
|
|
|
|
from tests.utils import MongoDBTestCase
|
|
|
|
|
|
class TestDateTimeField(MongoDBTestCase):
|
|
def test_datetime_from_empty_string(self):
|
|
"""
|
|
Ensure an exception is raised when trying to
|
|
cast an empty string to datetime.
|
|
"""
|
|
|
|
class MyDoc(Document):
|
|
dt = DateTimeField()
|
|
|
|
md = MyDoc(dt="")
|
|
with pytest.raises(ValidationError):
|
|
md.save()
|
|
|
|
def test_datetime_from_whitespace_string(self):
|
|
"""
|
|
Ensure an exception is raised when trying to
|
|
cast a whitespace-only string to datetime.
|
|
"""
|
|
|
|
class MyDoc(Document):
|
|
dt = DateTimeField()
|
|
|
|
md = MyDoc(dt=" ")
|
|
with pytest.raises(ValidationError):
|
|
md.save()
|
|
|
|
def test_default_value_utcnow(self):
|
|
"""Ensure that default field values are used when creating
|
|
a document.
|
|
"""
|
|
|
|
class Person(Document):
|
|
created = DateTimeField(default=dt.datetime.utcnow)
|
|
|
|
utcnow = dt.datetime.utcnow()
|
|
person = Person()
|
|
person.validate()
|
|
person_created_t0 = person.created
|
|
assert person.created - utcnow < dt.timedelta(seconds=1)
|
|
assert person_created_t0 == person.created # make sure it does not change
|
|
assert person._data["created"] == person.created
|
|
|
|
def test_handling_microseconds(self):
|
|
"""Tests showing pymongo datetime fields handling of microseconds.
|
|
Microseconds are rounded to the nearest millisecond and pre UTC
|
|
handling is wonky.
|
|
|
|
See: http://api.mongodb.org/python/current/api/bson/son.html#dt
|
|
"""
|
|
|
|
class LogEntry(Document):
|
|
date = DateTimeField()
|
|
|
|
LogEntry.drop_collection()
|
|
|
|
# Test can save dates
|
|
log = LogEntry()
|
|
log.date = dt.date.today()
|
|
log.save()
|
|
log.reload()
|
|
assert log.date.date() == dt.date.today()
|
|
|
|
# Post UTC - microseconds are rounded (down) nearest millisecond and
|
|
# dropped
|
|
d1 = dt.datetime(1970, 1, 1, 0, 0, 1, 999)
|
|
d2 = dt.datetime(1970, 1, 1, 0, 0, 1)
|
|
log = LogEntry()
|
|
log.date = d1
|
|
log.save()
|
|
log.reload()
|
|
assert log.date != d1
|
|
assert log.date == d2
|
|
|
|
# Post UTC - microseconds are rounded (down) nearest millisecond
|
|
d1 = dt.datetime(1970, 1, 1, 0, 0, 1, 9999)
|
|
d2 = dt.datetime(1970, 1, 1, 0, 0, 1, 9000)
|
|
log.date = d1
|
|
log.save()
|
|
log.reload()
|
|
assert log.date != d1
|
|
assert log.date == d2
|
|
|
|
def test_regular_usage(self):
|
|
"""Tests for regular datetime fields"""
|
|
|
|
class LogEntry(Document):
|
|
date = DateTimeField()
|
|
|
|
LogEntry.drop_collection()
|
|
|
|
d1 = dt.datetime(1970, 1, 1, 0, 0, 1)
|
|
log = LogEntry()
|
|
log.date = d1
|
|
log.validate()
|
|
log.save()
|
|
|
|
for query in (d1, d1.isoformat(" ")):
|
|
log1 = LogEntry.objects.get(date=query)
|
|
assert log == log1
|
|
|
|
if dateutil:
|
|
log1 = LogEntry.objects.get(date=d1.isoformat("T"))
|
|
assert log == log1
|
|
|
|
# create additional 19 log entries for a total of 20
|
|
for i in range(1971, 1990):
|
|
d = dt.datetime(i, 1, 1, 0, 0, 1)
|
|
LogEntry(date=d).save()
|
|
|
|
assert LogEntry.objects.count() == 20
|
|
|
|
# Test ordering
|
|
logs = LogEntry.objects.order_by("date")
|
|
i = 0
|
|
while i < 19:
|
|
assert logs[i].date <= logs[i + 1].date
|
|
i += 1
|
|
|
|
logs = LogEntry.objects.order_by("-date")
|
|
i = 0
|
|
while i < 19:
|
|
assert logs[i].date >= logs[i + 1].date
|
|
i += 1
|
|
|
|
# Test searching
|
|
logs = LogEntry.objects.filter(date__gte=dt.datetime(1980, 1, 1))
|
|
assert logs.count() == 10
|
|
|
|
logs = LogEntry.objects.filter(date__lte=dt.datetime(1980, 1, 1))
|
|
assert logs.count() == 10
|
|
|
|
logs = LogEntry.objects.filter(
|
|
date__lte=dt.datetime(1980, 1, 1), date__gte=dt.datetime(1975, 1, 1)
|
|
)
|
|
assert logs.count() == 5
|
|
|
|
def test_datetime_validation(self):
|
|
"""Ensure that invalid values cannot be assigned to datetime
|
|
fields.
|
|
"""
|
|
|
|
class LogEntry(Document):
|
|
time = DateTimeField()
|
|
|
|
log = LogEntry()
|
|
log.time = dt.datetime.now()
|
|
log.validate()
|
|
|
|
log.time = dt.date.today()
|
|
log.validate()
|
|
|
|
log.time = dt.datetime.now().isoformat(" ")
|
|
log.validate()
|
|
|
|
log.time = "2019-05-16 21:42:57.897847"
|
|
log.validate()
|
|
|
|
if dateutil:
|
|
log.time = dt.datetime.now().isoformat("T")
|
|
log.validate()
|
|
|
|
log.time = -1
|
|
with pytest.raises(ValidationError):
|
|
log.validate()
|
|
log.time = "ABC"
|
|
with pytest.raises(ValidationError):
|
|
log.validate()
|
|
log.time = "2019-05-16 21:GARBAGE:12"
|
|
with pytest.raises(ValidationError):
|
|
log.validate()
|
|
log.time = "2019-05-16 21:42:57.GARBAGE"
|
|
with pytest.raises(ValidationError):
|
|
log.validate()
|
|
log.time = "2019-05-16 21:42:57.123.456"
|
|
with pytest.raises(ValidationError):
|
|
log.validate()
|
|
|
|
def test_parse_datetime_as_str(self):
|
|
class DTDoc(Document):
|
|
date = DateTimeField()
|
|
|
|
date_str = "2019-03-02 22:26:01"
|
|
|
|
# make sure that passing a parsable datetime works
|
|
dtd = DTDoc()
|
|
dtd.date = date_str
|
|
assert isinstance(dtd.date, str)
|
|
dtd.save()
|
|
dtd.reload()
|
|
|
|
assert isinstance(dtd.date, dt.datetime)
|
|
assert str(dtd.date) == date_str
|
|
|
|
dtd.date = "January 1st, 9999999999"
|
|
with pytest.raises(ValidationError):
|
|
dtd.validate()
|
|
|
|
|
|
class TestDateTimeTzAware(MongoDBTestCase):
|
|
def test_datetime_tz_aware_mark_as_changed(self):
|
|
# Reset the connections
|
|
connection._connection_settings = {}
|
|
connection._connections = {}
|
|
connection._dbs = {}
|
|
|
|
connect(db="mongoenginetest", tz_aware=True)
|
|
|
|
class LogEntry(Document):
|
|
time = DateTimeField()
|
|
|
|
LogEntry.drop_collection()
|
|
|
|
LogEntry(time=dt.datetime(2013, 1, 1, 0, 0, 0)).save()
|
|
|
|
log = LogEntry.objects.first()
|
|
log.time = dt.datetime(2013, 1, 1, 0, 0, 0)
|
|
assert ["time"] == log._changed_fields
|