Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
b151d6c1
Commit
b151d6c1
authored
Jun 18, 2013
by
Don Mitchell
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #183 from edx/dhm/timeconv/bug0
Fix a few remaining tz naive dates
parents
a3c7ece8
a1f181e7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
87 additions
and
62 deletions
+87
-62
CHANGELOG.rst
+3
-0
cms/djangoapps/contentstore/tests/tests.py
+22
-0
common/lib/xmodule/xmodule/course_module.py
+10
-6
common/lib/xmodule/xmodule/fields.py
+26
-7
common/lib/xmodule/xmodule/tests/test_fields.py
+23
-0
common/lib/xmodule/xmodule/timeinfo.py
+3
-3
common/lib/xmodule/xmodule/timeparse.py
+0
-46
No files found.
CHANGELOG.rst
View file @
b151d6c1
...
...
@@ -53,6 +53,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
...
...
cms/djangoapps/contentstore/tests/tests.py
View file @
b151d6c1
...
...
@@ -3,6 +3,10 @@ from django.core.urlresolvers import reverse
from
.utils
import
parse_json
,
user
,
registration
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
contentstore.tests.test_course_settings
import
CourseTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
import
datetime
from
pytz
import
UTC
class
ContentStoreTestCase
(
ModuleStoreTestCase
):
...
...
@@ -162,3 +166,21 @@ class AuthTestCase(ContentStoreTestCase):
self
.
assertEqual
(
resp
.
status_code
,
302
)
# Logged in should work.
class
ForumTestCase
(
CourseTestCase
):
def
setUp
(
self
):
""" Creates the test course. """
super
(
ForumTestCase
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
(
org
=
'testX'
,
number
=
'727'
,
display_name
=
'Forum Course'
)
def
test_blackouts
(
self
):
now
=
datetime
.
datetime
.
now
(
UTC
)
self
.
course
.
discussion_blackouts
=
[(
t
.
isoformat
(),
t2
.
isoformat
())
for
t
,
t2
in
[(
now
-
datetime
.
timedelta
(
days
=
14
),
now
-
datetime
.
timedelta
(
days
=
11
)),
(
now
+
datetime
.
timedelta
(
days
=
24
),
now
+
datetime
.
timedelta
(
days
=
30
))]]
self
.
assertTrue
(
self
.
course
.
forum_posts_allowed
)
self
.
course
.
discussion_blackouts
=
[(
t
.
isoformat
(),
t2
.
isoformat
())
for
t
,
t2
in
[(
now
-
datetime
.
timedelta
(
days
=
14
),
now
+
datetime
.
timedelta
(
days
=
2
)),
(
now
+
datetime
.
timedelta
(
days
=
24
),
now
+
datetime
.
timedelta
(
days
=
30
))]]
self
.
assertFalse
(
self
.
course
.
forum_posts_allowed
)
common/lib/xmodule/xmodule/course_module.py
View file @
b151d6c1
...
...
@@ -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
):
date_proxy
=
Date
()
try
:
blackout_periods
=
[(
parse_time
(
start
),
parse_time
(
end
))
blackout_periods
=
[(
date_proxy
.
from_json
(
start
),
date_proxy
.
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
)
...
...
common/lib/xmodule/xmodule/fields.py
View file @
b151d6c1
...
...
@@ -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,28 @@ 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
PREVENT_DEFAULT_DAY_MON_SEED1
=
datetime
.
datetime
(
CURRENT_YEAR
,
1
,
1
,
tzinfo
=
UTC
)
PREVENT_DEFAULT_DAY_MON_SEED2
=
datetime
.
datetime
(
CURRENT_YEAR
,
2
,
2
,
tzinfo
=
UTC
)
def
_parse_date_wo_default_month_day
(
self
,
field
):
"""
Parse the field as an iso string but prevent dateutils from defaulting the day or month while
allowing it to default the other fields.
"""
# 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
.
PREVENT_DEFAULT_DAY_MON_SEED1
)
result_other
=
dateutil
.
parser
.
parse
(
field
,
default
=
self
.
PREVENT_DEFAULT_DAY_MON_SEED1
)
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
)
return
result
def
from_json
(
self
,
field
):
"""
Parse an optional metadata key containing a time: if present, complain
...
...
@@ -26,14 +48,11 @@ class Date(ModelType):
elif
field
is
""
:
return
None
elif
isinstance
(
field
,
basestring
):
result
=
dateutil
.
parser
.
parse
(
field
)
if
result
.
tzinfo
is
None
:
result
=
result
.
replace
(
tzinfo
=
UTC
())
return
result
return
self
.
_parse_date_wo_default_month_day
(
field
)
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
:
...
...
common/lib/xmodule/xmodule/tests/test_fields.py
View file @
b151d6c1
...
...
@@ -3,6 +3,8 @@ import datetime
import
unittest
from
django.utils.timezone
import
UTC
from
xmodule.fields
import
Date
,
Timedelta
from
xmodule.timeinfo
import
TimeInfo
import
time
class
DateTest
(
unittest
.
TestCase
):
...
...
@@ -52,6 +54,18 @@ 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_non_std_from_json
(
self
):
"""
Test the non-standard args being passed to from_json
"""
now
=
datetime
.
datetime
.
now
(
UTC
())
delta
=
now
-
datetime
.
datetime
.
fromtimestamp
(
0
,
UTC
())
self
.
assertEqual
(
DateTest
.
date
.
from_json
(
delta
.
total_seconds
()
*
1000
),
now
)
yesterday
=
datetime
.
datetime
.
now
(
UTC
())
-
datetime
.
timedelta
(
days
=-
1
)
self
.
assertEqual
(
DateTest
.
date
.
from_json
(
yesterday
),
yesterday
)
def
test_to_json
(
self
):
'''
...
...
@@ -90,3 +104,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
))
common/lib/xmodule/xmodule/timeinfo.py
View file @
b151d6c1
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
))
...
...
common/lib/xmodule/xmodule/timeparse.py
deleted
100644 → 0
View file @
a3c7ece8
"""
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
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment