232 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			232 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding: utf-8 -*-
 | |
| import datetime as dt
 | |
| import six
 | |
| 
 | |
| 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='')
 | |
|         self.assertRaises(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='   ')
 | |
|         self.assertRaises(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
 | |
|         self.assertLess(person.created - utcnow, dt.timedelta(seconds=1))
 | |
|         self.assertEqual(person_created_t0, person.created)  # make sure it does not change
 | |
|         self.assertEqual(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()
 | |
|         self.assertEqual(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()
 | |
|         self.assertNotEqual(log.date, d1)
 | |
|         self.assertEqual(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()
 | |
|         self.assertNotEqual(log.date, d1)
 | |
|         self.assertEqual(log.date, d2)
 | |
| 
 | |
|         if not six.PY3:
 | |
|             # Pre UTC dates microseconds below 1000 are dropped
 | |
|             # This does not seem to be true in PY3
 | |
|             d1 = dt.datetime(1969, 12, 31, 23, 59, 59, 999)
 | |
|             d2 = dt.datetime(1969, 12, 31, 23, 59, 59)
 | |
|             log.date = d1
 | |
|             log.save()
 | |
|             log.reload()
 | |
|             self.assertNotEqual(log.date, d1)
 | |
|             self.assertEqual(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)
 | |
|             self.assertEqual(log, log1)
 | |
| 
 | |
|         if dateutil:
 | |
|             log1 = LogEntry.objects.get(date=d1.isoformat('T'))
 | |
|             self.assertEqual(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()
 | |
| 
 | |
|         self.assertEqual(LogEntry.objects.count(), 20)
 | |
| 
 | |
|         # Test ordering
 | |
|         logs = LogEntry.objects.order_by("date")
 | |
|         i = 0
 | |
|         while i < 19:
 | |
|             self.assertTrue(logs[i].date <= logs[i + 1].date)
 | |
|             i += 1
 | |
| 
 | |
|         logs = LogEntry.objects.order_by("-date")
 | |
|         i = 0
 | |
|         while i < 19:
 | |
|             self.assertTrue(logs[i].date >= logs[i + 1].date)
 | |
|             i += 1
 | |
| 
 | |
|         # Test searching
 | |
|         logs = LogEntry.objects.filter(date__gte=dt.datetime(1980, 1, 1))
 | |
|         self.assertEqual(logs.count(), 10)
 | |
| 
 | |
|         logs = LogEntry.objects.filter(date__lte=dt.datetime(1980, 1, 1))
 | |
|         self.assertEqual(logs.count(), 10)
 | |
| 
 | |
|         logs = LogEntry.objects.filter(
 | |
|             date__lte=dt.datetime(1980, 1, 1),
 | |
|             date__gte=dt.datetime(1975, 1, 1),
 | |
|         )
 | |
|         self.assertEqual(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
 | |
|         self.assertRaises(ValidationError, log.validate)
 | |
|         log.time = 'ABC'
 | |
|         self.assertRaises(ValidationError, log.validate)
 | |
|         log.time = '2019-05-16 21:GARBAGE:12'
 | |
|         self.assertRaises(ValidationError, log.validate)
 | |
|         log.time = '2019-05-16 21:42:57.GARBAGE'
 | |
|         self.assertRaises(ValidationError, log.validate)
 | |
|         log.time = '2019-05-16 21:42:57.123.456'
 | |
|         self.assertRaises(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
 | |
|         self.assertIsInstance(dtd.date, six.string_types)
 | |
|         dtd.save()
 | |
|         dtd.reload()
 | |
| 
 | |
|         self.assertIsInstance(dtd.date, dt.datetime)
 | |
|         self.assertEqual(str(dtd.date), date_str)
 | |
| 
 | |
|         dtd.date = 'January 1st, 9999999999'
 | |
|         self.assertRaises(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)
 | |
|         self.assertEqual(['time'], log._changed_fields)
 |