Fix a few remaining tz naive dates
And remove redundant but != date parsing methods. In process make the general parsing function less lenient (don't default date nor month)
This commit is contained in:
@@ -34,6 +34,9 @@ Blades: Staff debug info is now accessible for Graphical Slider Tool problems.
|
||||
Blades: For Video Alpha the events ready, play, pause, seek, and speed change
|
||||
are logged on the server (in the logs).
|
||||
|
||||
Common: all dates and times are not time zone aware datetimes. No code should create or use struct_times nor naive
|
||||
datetimes.
|
||||
|
||||
Common: Developers can now have private Django settings files.
|
||||
|
||||
Common: Safety code added to prevent anything above the vertical level in the
|
||||
|
||||
@@ -10,7 +10,6 @@ import dateutil.parser
|
||||
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.seq_module import SequenceDescriptor, SequenceModule
|
||||
from xmodule.timeparse import parse_time
|
||||
from xmodule.util.decorators import lazyproperty
|
||||
from xmodule.graders import grader_from_conf
|
||||
import json
|
||||
@@ -645,8 +644,11 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
def start_date_text(self):
|
||||
def try_parse_iso_8601(text):
|
||||
try:
|
||||
result = datetime.strptime(text, "%Y-%m-%dT%H:%M")
|
||||
result = result.strftime("%b %d, %Y")
|
||||
result = Date().from_json(text)
|
||||
if result is None:
|
||||
result = text.title()
|
||||
else:
|
||||
result = result.strftime("%b %d, %Y")
|
||||
except ValueError:
|
||||
result = text.title()
|
||||
|
||||
@@ -670,8 +672,10 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
|
||||
@property
|
||||
def forum_posts_allowed(self):
|
||||
datestandin = Date()
|
||||
try:
|
||||
blackout_periods = [(parse_time(start), parse_time(end))
|
||||
blackout_periods = [(datestandin.from_json(start),
|
||||
datestandin.from_json(end))
|
||||
for start, end
|
||||
in self.discussion_blackouts]
|
||||
now = datetime.now(UTC())
|
||||
@@ -701,7 +705,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
if self.last_eligible_appointment_date is None:
|
||||
raise ValueError("Last appointment date must be specified")
|
||||
self.registration_start_date = (self._try_parse_time('Registration_Start_Date') or
|
||||
datetime.utcfromtimestamp(0))
|
||||
datetime.fromtimestamp(0, UTC()))
|
||||
self.registration_end_date = self._try_parse_time('Registration_End_Date') or self.last_eligible_appointment_date
|
||||
# do validation within the exam info:
|
||||
if self.registration_start_date > self.registration_end_date:
|
||||
@@ -720,7 +724,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
"""
|
||||
if key in self.exam_info:
|
||||
try:
|
||||
return parse_time(self.exam_info[key])
|
||||
return Date().from_json(self.exam_info[key])
|
||||
except ValueError as e:
|
||||
msg = "Exam {0} in course {1} loaded with a bad exam_info key '{2}': '{3}'".format(self.exam_name, self.course_id, self.exam_info[key], e)
|
||||
log.warning(msg)
|
||||
|
||||
@@ -6,7 +6,7 @@ from xblock.core import ModelType
|
||||
import datetime
|
||||
import dateutil.parser
|
||||
|
||||
from django.utils.timezone import UTC
|
||||
from pytz import UTC
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -15,6 +15,10 @@ class Date(ModelType):
|
||||
'''
|
||||
Date fields know how to parse and produce json (iso) compatible formats. Converts to tz aware datetimes.
|
||||
'''
|
||||
# See note below about not defaulting these
|
||||
CURRENT_YEAR = datetime.datetime.now(UTC).year
|
||||
DEFAULT_DATE0 = datetime.datetime(CURRENT_YEAR, 1, 1, tzinfo=UTC)
|
||||
DEFAULT_DATE1 = datetime.datetime(CURRENT_YEAR, 2, 2, tzinfo=UTC)
|
||||
def from_json(self, field):
|
||||
"""
|
||||
Parse an optional metadata key containing a time: if present, complain
|
||||
@@ -26,14 +30,21 @@ class Date(ModelType):
|
||||
elif field is "":
|
||||
return None
|
||||
elif isinstance(field, basestring):
|
||||
result = dateutil.parser.parse(field)
|
||||
# It's not trivial to replace dateutil b/c parsing timezones as Z, +03:30, -400 is hard in python
|
||||
# however, we don't want dateutil to default the month or day (but some tests at least expect
|
||||
# us to default year); so, we'll see if dateutil uses the defaults for these the hard way
|
||||
result = dateutil.parser.parse(field, default=self.DEFAULT_DATE0)
|
||||
result_other = dateutil.parser.parse(field, default=self.DEFAULT_DATE1)
|
||||
if result != result_other:
|
||||
log.warning("Field {0} is missing month or day".format(self._name, field))
|
||||
return None
|
||||
if result.tzinfo is None:
|
||||
result = result.replace(tzinfo=UTC())
|
||||
result = result.replace(tzinfo=UTC)
|
||||
return result
|
||||
elif isinstance(field, (int, long, float)):
|
||||
return datetime.datetime.fromtimestamp(field / 1000, UTC())
|
||||
return datetime.datetime.fromtimestamp(field / 1000, UTC)
|
||||
elif isinstance(field, time.struct_time):
|
||||
return datetime.datetime.fromtimestamp(time.mktime(field), UTC())
|
||||
return datetime.datetime.fromtimestamp(time.mktime(field), UTC)
|
||||
elif isinstance(field, datetime.datetime):
|
||||
return field
|
||||
else:
|
||||
|
||||
@@ -3,6 +3,7 @@ import datetime
|
||||
import unittest
|
||||
from django.utils.timezone import UTC
|
||||
from xmodule.fields import Date, Timedelta
|
||||
from xmodule.timeinfo import TimeInfo
|
||||
|
||||
|
||||
class DateTest(unittest.TestCase):
|
||||
@@ -52,6 +53,7 @@ class DateTest(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
datetime.datetime(current.year, 12, 4, 16, 30, tzinfo=UTC()),
|
||||
DateTest.date.from_json("December 4 16:30"))
|
||||
self.assertIsNone(DateTest.date.from_json("12 12:00"))
|
||||
|
||||
def test_to_json(self):
|
||||
'''
|
||||
@@ -90,3 +92,12 @@ class TimedeltaTest(unittest.TestCase):
|
||||
'1 days 46799 seconds',
|
||||
TimedeltaTest.delta.to_json(datetime.timedelta(days=1, hours=12, minutes=59, seconds=59))
|
||||
)
|
||||
|
||||
|
||||
class TimeInfoTest(unittest.TestCase):
|
||||
def test_time_info(self):
|
||||
due_date = datetime.datetime(2000, 4, 14, 10, tzinfo=UTC())
|
||||
grace_pd_string = '1 day 12 hours 59 minutes 59 seconds'
|
||||
timeinfo = TimeInfo(due_date, grace_pd_string)
|
||||
self.assertEqual(timeinfo.close_date,
|
||||
due_date + Timedelta().from_json(grace_pd_string))
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from .timeparse import parse_timedelta
|
||||
|
||||
import logging
|
||||
from xmodule.fields import Timedelta
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class TimeInfo(object):
|
||||
@@ -14,6 +13,7 @@ class TimeInfo(object):
|
||||
self.close_date - the real due date
|
||||
|
||||
"""
|
||||
_delta_standin = Timedelta()
|
||||
def __init__(self, due_date, grace_period_string):
|
||||
if due_date is not None:
|
||||
self.display_due_date = due_date
|
||||
@@ -23,7 +23,7 @@ class TimeInfo(object):
|
||||
|
||||
if grace_period_string is not None and self.display_due_date:
|
||||
try:
|
||||
self.grace_period = parse_timedelta(grace_period_string)
|
||||
self.grace_period = TimeInfo._delta_standin.from_json(grace_period_string)
|
||||
self.close_date = self.display_due_date + self.grace_period
|
||||
except:
|
||||
log.error("Error parsing the grace period {0}".format(grace_period_string))
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
"""
|
||||
Helper functions for handling time in the format we like.
|
||||
"""
|
||||
import re
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
TIME_FORMAT = "%Y-%m-%dT%H:%M"
|
||||
|
||||
TIMEDELTA_REGEX = re.compile(r'^((?P<days>\d+?) day(?:s?))?(\s)?((?P<hours>\d+?) hour(?:s?))?(\s)?((?P<minutes>\d+?) minute(?:s)?)?(\s)?((?P<seconds>\d+?) second(?:s)?)?$')
|
||||
|
||||
def parse_time(time_str):
|
||||
"""
|
||||
Takes a time string in TIME_FORMAT
|
||||
|
||||
Returns it as a time_struct.
|
||||
|
||||
Raises ValueError if the string is not in the right format.
|
||||
"""
|
||||
return datetime.strptime(time_str, TIME_FORMAT)
|
||||
|
||||
|
||||
def stringify_time(dt):
|
||||
"""
|
||||
Convert a datetime struct to a string
|
||||
"""
|
||||
return dt.isoformat()
|
||||
|
||||
def parse_timedelta(time_str):
|
||||
"""
|
||||
time_str: A string with the following components:
|
||||
<D> day[s] (optional)
|
||||
<H> hour[s] (optional)
|
||||
<M> minute[s] (optional)
|
||||
<S> second[s] (optional)
|
||||
|
||||
Returns a datetime.timedelta parsed from the string
|
||||
"""
|
||||
parts = TIMEDELTA_REGEX.match(time_str)
|
||||
if not parts:
|
||||
return
|
||||
parts = parts.groupdict()
|
||||
time_params = {}
|
||||
for (name, param) in parts.iteritems():
|
||||
if param:
|
||||
time_params[name] = int(param)
|
||||
return timedelta(**time_params)
|
||||
Reference in New Issue
Block a user