Skip to content

Commit cd037c6

Browse files
authored
Fix datetime field/widget shows current date and time if empty (senaite#1907)
* Fix datetime widget shows current date if empty This also happened for older `datetime.date` objects in the DB * Changelog updated * Support date strings in date API * Raise API Error instead
1 parent 14eff7b commit cd037c6

File tree

5 files changed

+97
-9
lines changed

5 files changed

+97
-9
lines changed

CHANGES.rst

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Changelog
44
2.2.0 (unreleased)
55
------------------
66

7+
- #1907 Fix datetime field/widget shows current date and time if empty
78
- #1905 Fix empty field in sample add form when using edit accessor
89

910

src/senaite/core/api/dtime.py

+44-6
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,24 @@
55
from datetime import date
66
from datetime import datetime
77

8+
import six
9+
810
import pytz
911
from bika.lims import logger
12+
from bika.lims.api import APIError
1013
from DateTime import DateTime
14+
from DateTime.DateTime import DateError
15+
from DateTime.DateTime import SyntaxError
16+
from DateTime.DateTime import TimeError
17+
18+
19+
def is_str(obj):
20+
"""Check if the given object is a string
21+
22+
:param obj: arbitrary object
23+
:returns: True when the object is a string
24+
"""
25+
return isinstance(obj, six.string_types)
1126

1227

1328
def is_d(dt):
@@ -43,6 +58,9 @@ def is_date(dt):
4358
:param dt: date to check
4459
:returns: True when the object is either a datetime or DateTime
4560
"""
61+
if is_str(dt):
62+
DT = to_DT(dt)
63+
return is_date(DT)
4664
if is_d(dt):
4765
return True
4866
if is_dt(dt):
@@ -64,7 +82,10 @@ def is_timezone_naive(dt):
6482
return dt.timezoneNaive()
6583
elif is_dt(dt):
6684
return dt.tzinfo is None
67-
raise TypeError("Expected a date, got '%r'" % type(dt))
85+
elif is_str(dt):
86+
DT = to_DT(dt)
87+
return is_timezone_naive(DT)
88+
raise APIError("Expected a date type, got '%r'" % type(dt))
6889

6990

7091
def is_timezone_aware(dt):
@@ -84,12 +105,18 @@ def to_DT(dt):
84105
"""
85106
if is_DT(dt):
86107
return dt
108+
elif is_str(dt):
109+
try:
110+
return DateTime(dt)
111+
except (DateError, TimeError, SyntaxError, IndexError):
112+
return None
87113
elif is_dt(dt):
88114
return DateTime(dt.isoformat())
89115
elif is_d(dt):
90116
dt = datetime(dt.year, dt.month, dt.day)
91117
return DateTime(dt.isoformat())
92-
raise TypeError("Expected datetime, got '%r'" % type(dt))
118+
else:
119+
return None
93120

94121

95122
def to_dt(dt):
@@ -100,11 +127,15 @@ def to_dt(dt):
100127
"""
101128
if is_DT(dt):
102129
return dt.asdatetime()
130+
elif is_str(dt):
131+
DT = to_DT(dt)
132+
return to_dt(DT)
103133
elif is_dt(dt):
104134
return dt
105135
elif is_d(dt):
106136
return datetime(dt.year, dt.month, dt.day)
107-
raise TypeError("Expected DateTime, got '%r'" % type(dt))
137+
else:
138+
return None
108139

109140

110141
def is_valid_timezone(timezone):
@@ -155,14 +186,16 @@ def to_zone(dt, timezone):
155186
:param timezone: timezone
156187
:returns: date converted to timezone
157188
"""
158-
if is_dt(dt):
189+
if is_dt(dt) or is_d(dt):
190+
dt = to_dt(dt)
159191
zone = pytz.timezone(timezone)
160192
if is_timezone_aware(dt):
161193
return dt.astimezone(zone)
162194
return zone.localize(dt)
163-
if is_DT(dt):
195+
elif is_DT(dt):
164196
# NOTE: This shifts the time according to the TZ offset
165197
return dt.toZone(timezone)
198+
raise TypeError("Expected a date, got '%r'" % type(dt))
166199

167200

168201
def to_timestamp(dt):
@@ -176,6 +209,9 @@ def to_timestamp(dt):
176209
timestamp = dt.timeTime()
177210
elif is_dt(dt):
178211
timestamp = time.mktime(dt.timetuple())
212+
elif is_str(dt):
213+
DT = to_DT(dt)
214+
return to_timestamp(DT)
179215
return timestamp
180216

181217

@@ -185,7 +221,6 @@ def from_timestamp(timestamp):
185221
:param timestamp: POSIX timestamp
186222
:returns: datetime object
187223
"""
188-
189224
return datetime.utcfromtimestamp(timestamp)
190225

191226

@@ -196,4 +231,7 @@ def to_iso_format(dt):
196231
return dt.isoformat()
197232
elif is_DT(dt):
198233
return dt.ISO()
234+
elif is_str(dt):
235+
DT = to_DT(dt)
236+
return to_iso_format(DT)
199237
return None

src/senaite/core/schema/datetimefield.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ def set(self, object, value):
3232
:param value: datetime value
3333
:type value: datetime
3434
"""
35-
if dtime.is_dt(value):
36-
value = localize(value)
35+
if dtime.is_date(value):
36+
value = localize(dtime.to_dt(value))
3737
super(DatetimeField, self).set(object, value)
3838

3939
def get(self, object):
@@ -43,8 +43,12 @@ def get(self, object):
4343
:returns: datetime or None
4444
"""
4545
value = super(DatetimeField, self).get(object)
46-
if not dtime.is_dt(value):
46+
# bail out if value is not a known date object
47+
if not dtime.is_date(value):
4748
return None
49+
# ensure we have a `datetime` object
50+
value = dtime.to_dt(value)
51+
# always return localized datetime objects
4852
return localize(value)
4953

5054
def _validate(self, value):

src/senaite/core/tests/doctests/API_datetime.rst

+43
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,15 @@ Check if an object represents a date
8585
True
8686

8787
>>> dtime.is_date("2021-12-24")
88+
True
89+
90+
>>> dtime.is_date("2021-12-24T12:00:00")
91+
True
92+
93+
>>> dtime.is_date("2021-12-24T12:00:00+01:00")
94+
True
95+
96+
>>> dtime.is_date("Hello World")
8897
False
8998

9099
>>> dtime.is_date(object())
@@ -103,6 +112,15 @@ Check if a datetime object is TZ naive
103112
>>> dtime.is_timezone_naive(DateTime())
104113
False
105114

115+
>>> dtime.is_timezone_naive("2021-12-24")
116+
True
117+
118+
>>> dtime.is_timezone_naive("2021-12-24T12:00:00")
119+
True
120+
121+
>>> dtime.is_timezone_naive("2021-12-24T12:00:00+01:00")
122+
False
123+
106124

107125
Check if a datetime object is TZ aware
108126
......................................
@@ -116,6 +134,15 @@ Check if a datetime object is TZ aware
116134
>>> dtime.is_timezone_aware(DateTime())
117135
True
118136

137+
>>> dtime.is_timezone_aware("2021-12-24")
138+
False
139+
140+
>>> dtime.is_timezone_aware("2021-12-24T12:00:00")
141+
False
142+
143+
>>> dtime.is_timezone_aware("2021-12-24T12:00:00+01:00")
144+
True
145+
119146

120147
Convert to DateTime
121148
...................
@@ -128,6 +155,9 @@ Timezone naive datetimes are converterd to `GMT+0`:
128155
>>> dt
129156
datetime.datetime(2021, 12, 24, 12, 0)
130157

158+
>>> dtime.to_DT(DATE)
159+
DateTime('2021/12/24 12:00:00 GMT+0')
160+
131161
>>> dtime.to_DT(dt)
132162
DateTime('2021/12/24 12:00:00 GMT+0')
133163

@@ -233,6 +263,13 @@ Convert `datetime` objects to a timezone:
233263
>>> dtime.to_zone(dt_utc, "CET")
234264
datetime.datetime(1970, 1, 1, 2, 0, tzinfo=<DstTzInfo 'CET' CET+1:00:00 STD>)
235265

266+
Convert `date` objects to a timezone (converts to `datetime`):
267+
268+
>>> d = date.fromordinal(dt.toordinal())
269+
>>> d_utc = dtime.to_zone(d, "UTC")
270+
>>> d_utc
271+
datetime.datetime(1970, 1, 1, 0, 0, tzinfo=<UTC>)
272+
236273
Convert `DateTime` objects to a timezone:
237274

238275
>>> DT = DateTime(DATE)
@@ -252,6 +289,9 @@ Make a POSIX timestamp
252289
>>> DT = DateTime(DATE)
253290
>>> dt = datetime.strptime(DATE, DATEFORMAT)
254291

292+
>>> dtime.to_timestamp(DATE)
293+
3600.0
294+
255295
>>> dtime.to_timestamp(dt)
256296
3600.0
257297

@@ -271,6 +311,9 @@ Convert to ISO format
271311
>>> dt_local
272312
datetime.datetime(2021, 8, 1, 12, 0, tzinfo=<DstTzInfo 'CET' CEST+2:00:00 DST>)
273313

314+
>>> dtime.to_iso_format(DATE)
315+
'2021-08-01T12:00:00'
316+
274317
>>> dtime.to_iso_format(dt_local)
275318
'2021-08-01T12:00:00+02:00'
276319

src/senaite/core/z3cform/widgets/datetimewidget.py

+2
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ def get_display_value(self):
167167
if dm:
168168
# extract the object from the database
169169
value = dm.query()
170+
if not dtime.is_date(value):
171+
return None
170172
return self.to_localized_time(value)
171173

172174
def to_datetime(self, value):

0 commit comments

Comments
 (0)