Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[frontend] Timezone and date format for date display and datetime pickers #988

Open
wants to merge 104 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
2a24526
[frontend/tasksets] Changing course accessibility init structure
AlexandreDoneux Nov 14, 2023
78449e8
[frontend/accessible_time] Changing AccessibleTime to new course/task…
AlexandreDoneux Nov 14, 2023
d8e22f8
[frontend/course_admin] Adapting course settings form to new course a…
AlexandreDoneux Nov 14, 2023
ae87e9d
[frontend/accessibility] Changing task accessibility handling for tas…
AlexandreDoneux Nov 14, 2023
5f95381
[frontend/task_list] processing accessibility_period before updating …
AlexandreDoneux Nov 14, 2023
3437e2e
[frontend] Changing task accessibility data structure
AlexandreDoneux Nov 14, 2023
98e8396
[frontend] changing course acces and registration structure in yaml a…
AlexandreDoneux Nov 14, 2023
18a8daf
[frontend/toc] fixing dates transformation before rendering toc.html
AlexandreDoneux Nov 16, 2023
7ae3a78
[frontend] Storing dates in yaml files as datetime objects
AlexandreDoneux Nov 20, 2023
0340138
[frontend/accessible_time] Changing AccessibleTime init structure
AlexandreDoneux Nov 22, 2023
96231dd
[frontend] Storing datetime to string and the opposite in util files
AlexandreDoneux Nov 23, 2023
fa0750a
[frontend] Adapting combinatory test to new AccessibleTime structure
AlexandreDoneux Nov 23, 2023
f9aee73
[frontend] Changing datetime to str and opposite
AlexandreDoneux Nov 28, 2023
9c0473d
[frontend/task_dispenser_admin] Allowing string dates in AccessibleTi…
AlexandreDoneux Nov 30, 2023
0df58a4
[frontend/task_dispenser] Adapting Accessibility.get_value() to legac…
AlexandreDoneux Nov 30, 2023
1c7ba27
Â[frontend] Remove boolean from access data structure
AlexandreDoneux Dec 6, 2023
2340aea
[common/custom_yaml] Changing timestamp representer to correctly tran…
AlexandreDoneux Dec 6, 2023
fd25ba3
[frontend/accessibility.html] Fix error occuring when changing date o…
AlexandreDoneux Dec 6, 2023
ed56453
[frontend] Fixing max date milliseconds not written in yaml and DB
AlexandreDoneux Dec 8, 2023
19c0500
[frontend/accessibility] Fixing grouped actions task edit
AlexandreDoneux Dec 8, 2023
a789634
[frontend] Changing accessible and registration structure when import…
AlexandreDoneux Dec 12, 2023
6ea6b02
[frontend] Changing task access structure during imports in task_disp…
AlexandreDoneux Dec 13, 2023
0fe5ec1
[plugins/contest] fixing method name change from TasksetFactory
AlexandreDoneux Dec 13, 2023
1d50870
[plugins/contests] Fixing contest edits saved in taskset.yaml instead…
AlexandreDoneux Dec 13, 2023
7a7d03f
[plugins/contests] Adapting contest plugin to new access structure
AlexandreDoneux Dec 13, 2023
7a6b750
[frontend/accessible_time] Refactoring parameter check for Accessible…
AlexandreDoneux Dec 13, 2023
39ce930
cleaning for PR
AlexandreDoneux Dec 14, 2023
0998b0b
[frontend/accessibility] Fixing modal feed to all modal instead of sp…
AlexandreDoneux Jan 4, 2024
96b2446
[frontend/accessibility] Fixing datetime picker cleaning during modal…
AlexandreDoneux Jan 5, 2024
d25e5a0
[frontend/contest] Fixing constest plugin with new database date format
AlexandreDoneux Jan 25, 2024
ea4df4e
[frontend/constest] Fixing no soft_end given in contest .get_accessib…
AlexandreDoneux Jan 26, 2024
c6685c3
[frontend] Indicating a four digit year when transforming datetimes i…
AlexandreDoneux Apr 16, 2024
9b5416d
[custom_yaml] refactoring formatting for datetime yaml representer
AlexandreDoneux Apr 16, 2024
9395f96
[frontend] Remove min and max hardcoded dates + storing without micro…
AlexandreDoneux Apr 17, 2024
26867c7
[frontend/accessibleTime] Remove method adapting database time
AlexandreDoneux Apr 17, 2024
085fef4
[frontaned/accessible_time] Adding support for legacy time format
AlexandreDoneux Apr 18, 2024
f1a3f4c
[frontend/accessible_time] Fixing legacy structure support
AlexandreDoneux Apr 26, 2024
96b0d45
[frontend/accessible_time] Adapting course accessibility when no regi…
AlexandreDoneux Apr 29, 2024
d56d8fd
[frontend/settings] Changing course accessibility structure when upda…
AlexandreDoneux Apr 29, 2024
4f7f40a
[frontend/task_list] Moving task access structure update to legacy up…
AlexandreDoneux Apr 29, 2024
b615b92
Revert "[frontend/task_list] Moving task access structure update to l…
AlexandreDoneux May 6, 2024
26e0658
[frontend/task_dispenser] Updating task accessibility structure when …
AlexandreDoneux May 6, 2024
5776d6e
Fixing Codacy warnings
AlexandreDoneux May 7, 2024
ca8e595
[frontend/accessible_time] Fix legacy accessibility string structure …
AlexandreDoneux May 7, 2024
dafa720
Fix codacy warning
AlexandreDoneux May 7, 2024
1a88ba2
[frontend/accessible_time] Using self.min and max in legacy_string_st…
AlexandreDoneux May 16, 2024
42811b0
[frontend] Fixing forgotten code that needed to be removed
AlexandreDoneux May 16, 2024
fed730d
[frontend/util] Removing util.py file with unused method
AlexandreDoneux May 16, 2024
05daaf2
[frontend/accessible_time] Fixing soft_end set to end when soft_end n…
AlexandreDoneux May 17, 2024
02937c1
[frontend] Adapting combinatory test to new AccessibleTime structure
AlexandreDoneux Nov 23, 2023
672f4a0
[frontend/task_dispenser_admin] Allowing string dates in AccessibleTi…
AlexandreDoneux Nov 30, 2023
71515f6
Adding moment.js locales and moment-timezone.js
AlexandreDoneux Oct 30, 2023
772a5d3
[frontend] Replacing moment-timezone library file to have timezone data
AlexandreDoneux Oct 31, 2023
31c63d5
[frontend] adding timezone to user info and apply it to datetimepicker
AlexandreDoneux Oct 31, 2023
8d2b22c
Changing moment-timezone file to include future data
AlexandreDoneux Nov 2, 2023
6b57cf4
[frontend] fixing timeZone typo in settings.html
AlexandreDoneux Nov 2, 2023
1a5218c
[frontend] Adding timeZone to last datetimepickers
AlexandreDoneux Nov 3, 2023
974aa11
[frontend/user_manager] Adding timezone data into UserManager to be m…
AlexandreDoneux Nov 3, 2023
f29900e
[frontend/profile] Adding timezone preference in profile edit
AlexandreDoneux Dec 21, 2023
ae61c79
[frontend/course_admin] Adding reverse timezone transformation before…
AlexandreDoneux Dec 21, 2023
be4cc2a
[frontend/accessible_time] Using UTC dates during accessibility check
AlexandreDoneux Jan 2, 2024
4b6fc17
[frontend/accessible_time] Adding min and max values based on utc whe…
AlexandreDoneux Jan 3, 2024
352f3e1
[frontend/accessibility] Fixing taskid use in accessibility.html
AlexandreDoneux Jan 5, 2024
32f366f
[frontend/accessibility] Applying timezone to task accessibility edit
AlexandreDoneux Jan 9, 2024
13411ad
[frontend/accessibility] Applying timezone handling for grouped edit …
AlexandreDoneux Jan 10, 2024
62b6258
[frontend/accessibility] Fixing grouped custom edit changes without c…
AlexandreDoneux Jan 10, 2024
9de79ca
[frontend/submission_query] Applying timezone to datetime pickers in …
AlexandreDoneux Jan 12, 2024
2537a09
[frontend/settings] Fixing empty datetime picker sends creation date
AlexandreDoneux Jan 12, 2024
590ddff
[frontend/accessibility] Changing 'date' picker option instead of des…
AlexandreDoneux Jan 15, 2024
e855325
[frontend] Adding Flatpicker js and css files
AlexandreDoneux Jan 19, 2024
5af386a
Fix typo in Flatpickr file names
AlexandreDoneux Jan 19, 2024
270f834
[frontend/settings] Adapt course settings to use Flatpickr instead of…
AlexandreDoneux Jan 22, 2024
f763c67
[frontend/accessibility] Adapting accessibility feed and individual c…
AlexandreDoneux Jan 22, 2024
15d1def
[frontend/accessibility] Adapting grouped task edit to use Flatpickr
AlexandreDoneux Jan 23, 2024
7b386dc
[frontend/submissions_query] Adapting submissions query to use flatpi…
AlexandreDoneux Jan 23, 2024
b8cfd82
[frontend/contest] Adapting contest admin to use Flatpickr datetime p…
AlexandreDoneux Jan 23, 2024
ffb9151
[frontend] Adding locales to flatpickr pickers
AlexandreDoneux Jan 23, 2024
9e2a37b
[frontend] fixing task accessibility datetime picker
AlexandreDoneux Jan 24, 2024
e65edd7
[frontend] Fixing missing utc timezone for date comparison
AlexandreDoneux Jan 25, 2024
ce0959e
[frontend/upcoming_tasks] Fixing outdated tasks and course method cal…
AlexandreDoneux Jan 25, 2024
d630f82
[frontend/contest] Fix flatpickr locale
AlexandreDoneux Jan 25, 2024
b6b5fb0
[frontend/statistics] Fixing missing timezone in datetime key
AlexandreDoneux Jan 25, 2024
07cbe29
[frontend] Displaying dates depending on the user's timezone
AlexandreDoneux Jan 26, 2024
7379c7b
[frontend/contest] Fixing wrong class name for datetime display
AlexandreDoneux Jan 26, 2024
23bfd63
[frontend/contest] Manage contest access with utc comparison
AlexandreDoneux Jan 29, 2024
f081fbd
[frontend] Fixing datetime picker default values
AlexandreDoneux Jan 29, 2024
ebda52e
[frontend/contest] Adapt contest countdowns to user's timezone
AlexandreDoneux Jan 29, 2024
a007679
[frontend/scoreboard] Add missing class and timezone attribute to ada…
AlexandreDoneux Jan 29, 2024
8949c4a
[frontend] Browser timezone by default
AlexandreDoneux Jan 31, 2024
f9d3741
[frontend] Adding datetime format in user preference
AlexandreDoneux Feb 2, 2024
f181093
[frontend] Changing available_datetime_formats structure
AlexandreDoneux Feb 2, 2024
f3727f5
[frontend/settings] Adapting course settings to date format
AlexandreDoneux Feb 2, 2024
1ff729c
[frontend/submissions_query] Adapting submissions_query to date format
AlexandreDoneux Feb 2, 2024
a14c18f
[frontend/contest] Adapting contest settings to date format
AlexandreDoneux Feb 2, 2024
ae572b6
[frontend/accessibility] Adapt accessibility task setting to date format
AlexandreDoneux Feb 2, 2024
2065f77
[frontend] Applying date format to displayed dates
AlexandreDoneux Feb 5, 2024
965af5b
[frontend/accessibility] Applying date format to displayed accessibil…
AlexandreDoneux Feb 5, 2024
ee23a48
[frontend] Fix datetime picker minute and second increment to 1
AlexandreDoneux Feb 5, 2024
1e5ef76
[frontend] Adapt datetime picker placeholders to date format
AlexandreDoneux Feb 5, 2024
282a1db
[frontend/accessible_time] Applying utc timezone directly in parse_da…
AlexandreDoneux Feb 28, 2024
d6ea6f5
[frontend/profile] Adding missing check for timezone and date format …
AlexandreDoneux May 16, 2024
8ba784c
[frontend/user_manager] Fixing missing arguments in self.connect_user…
AlexandreDoneux May 16, 2024
6ae1b50
[frontend/accessibility] Applying utc timezone to min and max and fix…
AlexandreDoneux May 17, 2024
cf2955c
[frontend/accessible_time] Fixing missing utc timezone in condition
AlexandreDoneux May 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions inginious/common/custom_yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

""" A custom YAML based on PyYAML, that provides Ordered Dicts """
# Most ideas for this implementation comes from http://stackoverflow.com/questions/5121931/in-python-how-can-you-load-yaml-mappings-as-ordereddicts
from datetime import datetime

from collections import OrderedDict

import yaml as original_yaml
Expand Down Expand Up @@ -74,10 +76,16 @@ def _long_str_representer(dumper, data):
def _default_representer(dumper, data):
return _long_str_representer(dumper, str(data))

def _timestamp_representer(dumper, data):
date = data.strftime("%4Y-%m-%dT%H:%M:%SZ")
return dumper.represent_scalar('tag:yaml.org,2002:timestamp', date)


OrderedDumper.add_representer(str, _long_str_representer)
OrderedDumper.add_representer(str, _long_str_representer)
OrderedDumper.add_representer(OrderedDict, _dict_representer)
OrderedDumper.add_representer(None, _default_representer)
OrderedDumper.add_representer(datetime, _timestamp_representer)

s = original_yaml.dump(data, stream, OrderedDumper, encoding='utf-8', allow_unicode=True, default_flow_style=False, indent=4, **kwds)

Expand Down
190 changes: 119 additions & 71 deletions inginious/frontend/accessible_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,31 @@

""" Contains AccessibleTime, class that represents the period of time when a course/task is accessible """

from datetime import datetime
from datetime import datetime, timezone


def parse_date(date, default=None):
""" Parse a valid date """
"""
Parse a valid date
:param date: string, date to parse
:param default: datetime object, optionnal, default value to return if date is empty
:return: datetime object of the parsed date
"""
if date == "":
if default is not None:
return default
else:
raise Exception("Unknown format for " + date)
raise Exception("Empty date given to AccessibleTime")

for format_type in ["%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%d %H", "%Y-%m-%d", "%d/%m/%Y %H:%M:%S", "%d/%m/%Y %H:%M", "%d/%m/%Y %H",
"%d/%m/%Y"]:
if date == "0001-01-01 00:00:00":
return datetime.min.replace(tzinfo=timezone.utc)
if date == "9999-12-31 23:59:59":
return datetime.max.replace(microsecond=0, tzinfo=timezone.utc)

for format_type in ["%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%d %H", "%Y-%m-%d", "%d/%m/%Y %H:%M:%S",
"%d/%m/%Y %H:%M", "%d/%m/%Y %H", "%d/%m/%Y"]:
try:
return datetime.strptime(date, format_type)
return datetime.strptime(date, format_type).replace(tzinfo=timezone.utc)
except ValueError:
pass
raise Exception("Unknown format for " + date)
Expand All @@ -28,55 +38,78 @@ def parse_date(date, default=None):
class AccessibleTime(object):
""" represents the period of time when a course/task is accessible """

def __init__(self, val=None):
def __init__(self, period):
"""
Parse a string/a boolean to get the correct time period.
Correct values for val:
True (task always open)
False (task always closed)
2014-07-16 11:24:00 (task is open from 2014-07-16 at 11:24:00)
2014-07-16 (task is open from 2014-07-16)
/ 2014-07-16 11:24:00 (task is only open before the 2014-07-16 at 11:24:00)
/ 2014-07-16 (task is only open before the 2014-07-16)
2014-07-16 11:24:00 / 2014-07-20 11:24:00 (task is open from 2014-07-16 11:24:00 and will be closed the 2014-07-20 at 11:24:00)
2014-07-16 / 2014-07-20 11:24:00 (...)
2014-07-16 11:24:00 / 2014-07-20 (...)
2014-07-16 / 2014-07-20 (...)
2014-07-16 11:24:00 / 2014-07-20 11:24:00 / 2014-07-20 12:24:00 (task is open from 2014-07-16 11:24:00, has a soft deadline set at 2014-07-20 11:24:00 and will be closed the 2014-07-20 at 11:24:00)
2014-07-16 / 2014-07-20 11:24:00 / 2014-07-21 (...)
2014-07-16 / 2014-07-20 / 2014-07-21 (...)
Used to represent the period of time when a course/task is accessible.
:param period : dict, contains start, end and optionally soft_end as datetime objects or strings
(for frontend use through templates).
Can be a boolean, None or string if using the legacy format "start/soft_end/end"
"""
if val is None or val == "" or val is True:
self._val = [datetime.min, datetime.max]
self._soft_end = datetime.max
elif val == False:
self._val = [datetime.max, datetime.max]
self._soft_end = datetime.max
else: # str
values = val.split("/")
if len(values) == 1:
self._val = [parse_date(values[0].strip(), datetime.min), datetime.max]
self._soft_end = datetime.max
elif len(values) == 2:
# Has start time and hard deadline
self._val = [parse_date(values[0].strip(), datetime.min), parse_date(values[1].strip(), datetime.max)]
self._soft_end = self._val[1]
else:
# Has start time, soft deadline and hard deadline
self._val = [parse_date(values[0].strip(), datetime.min), parse_date(values[2].strip(), datetime.max)]
self._soft_end = parse_date(values[1].strip(), datetime.max)

# Having a soft deadline after the hard one does not make sense, make soft-deadline same as hard-deadline
if self._soft_end > self._val[1]:
self._soft_end = self._val[1]
self.max = datetime.max.replace(microsecond=0, tzinfo=timezone.utc)
self.min = datetime.min.replace(tzinfo=timezone.utc)

if not isinstance(period, (dict, str, bool, type(None))): # add None check
raise Exception("Wrong period given to AccessibleTime")

# if legacy format (start/soft_end/end string, empty string, bool)
if isinstance(period, str):
period = self.legacy_string_structure_to_dict(period)
if isinstance(period, (bool, type(None))):
if period is (True or None):
period = {"start": self.min, "soft_end": self.max, "end": self.max}
else:
period = {"start": self.max, "soft_end": self.max, "end": self.max}

# transforming strings into datetimes in case AccessibleTime is used in html files, where datetime objects are not supported
for key, date in period.items():
if not isinstance(date, (datetime, str)):
raise Exception("Wrong period given to AccessibleTime")
if isinstance(date, str):
period[key] = parse_date(date)

self._start = period["start"].replace(tzinfo=timezone.utc) if period["start"] not in [self.min, self.max] else period["start"]
self._end = period["end"].replace(tzinfo=timezone.utc) if period["end"] != self.max else period["end"]
if "soft_end" in period:
if period["soft_end"].replace(tzinfo=timezone.utc) == self.max:
self._soft_end = self.max
else:
soft_end = min(period["soft_end"], period["end"])
self._soft_end = soft_end.replace(tzinfo=timezone.utc)

def legacy_string_structure_to_dict(self, legacy_date):
"""
Convert the legacy string structure to a dictionary. The legacy structure follows "start/soft_end/end" for
tasks or "start/end" for courses with some of the values being optional. Sometimes only a start date is
given as a string (ex: "start//end", "start//", "//end", "start/end", "start", "/end", ...).
:param legacy_date: string, legacy date structure
:return period: dict, containing the start, soft_end and end as strings
"""
period = {}

values = legacy_date.split("/")
if len(values) == 1:
period["start"] = parse_date(values[0].strip(), self.min)
period["soft_end"] = self.max
period["end"] = self.max
elif len(values) == 2:
# Has start time and hard deadline
period["start"] = parse_date(values[0].strip(), self.min)
period["end"] = parse_date(values[1].strip(), self.max)
period["soft_end"] = period["end"]
else:
# Has start time, soft deadline and hard deadline
period["start"] = parse_date(values[0].strip(), self.min)
period["soft_end"] = parse_date(values[1].strip(), self.max)
period["end"] = parse_date(values[2].strip(), self.max)
return period

def before_start(self, when=None):
""" Returns True if the task/course is not yet accessible """
if when is None:
when = datetime.now()
when = datetime.now(timezone.utc)

return self._val[0] > when
return self._start > when

def after_start(self, when=None):
""" Returns True if the task/course is or have been accessible in the past """
Expand All @@ -85,56 +118,71 @@ def after_start(self, when=None):
def is_open(self, when=None):
""" Returns True if the course/task is still open """
if when is None:
when = datetime.now()
when = datetime.now(timezone.utc)

return self._val[0] <= when and when <= self._val[1]
return self._start <= when <= self._end

def is_open_with_soft_deadline(self, when=None):
""" Returns True if the course/task is still open with the soft deadline """
if when is None:
when = datetime.now()
when = datetime.now(timezone.utc)

return self._val[0] <= when and when <= self._soft_end
return self._start <= when <= self._soft_end

def is_always_accessible(self):
""" Returns true if the course/task is always accessible """
return self._val[0] == datetime.min and self._val[1] == datetime.max
return self._start == self.min and self._end == self.max

def is_never_accessible(self):
""" Returns true if the course/task is never accessible """
return self._val[0] == datetime.max and self._val[1] == datetime.max
return self._start == self.max and self._end == self.max

def get_std_start_date(self):
""" If the date is custom, return the start datetime with the format %Y-%m-%d %H:%M:%S. Else, returns "". """
first, _ = self._val
if first != datetime.min and first != datetime.max:
return first.strftime("%Y-%m-%d %H:%M:%S")
else:
return ""
""" If the date is custom, return the start datetime with the format %4Y-%m-%d %H:%M:%S. Else, returns "". """
if self._start not in [self.min, self.max]:
return self._start.strftime("%4Y-%m-%d %H:%M:%S")
return ""

def get_std_end_date(self):
""" If the date is custom, return the end datetime with the format %Y-%m-%d %H:%M:%S. Else, returns "". """
_, second = self._val
if second != datetime.max:
return second.strftime("%Y-%m-%d %H:%M:%S")
else:
return ""
""" If the date is custom, return the end datetime with the format %4Y-%m-%d %H:%M:%S. Else, returns "". """
if self._end != self.max:
return self._end.strftime("%4Y-%m-%d %H:%M:%S")
return ""

def get_std_soft_end_date(self):
""" If the date is custom, return the soft datetime with the format %Y-%m-%d %H:%M:%S. Else, returns "". """
if self._soft_end != datetime.max:
return self._soft_end.strftime("%Y-%m-%d %H:%M:%S")
else:
return ""
""" If the date is custom, return the soft datetime with the format %4Y-%m-%d %H:%M:%S. Else, returns "". """
if self._soft_end != self.max:
return self._soft_end.strftime("%4Y-%m-%d %H:%M:%S")
return ""

def get_start_date(self):
""" Return a datetime object, representing the date when the task/course become accessible """
return self._val[0]
return self._start

def get_end_date(self):
""" Return a datetime object, representing the deadline for accessibility """
return self._val[1]
return self._end

def get_soft_end_date(self):
""" Return a datetime object, representing the soft deadline for accessibility """
return self._soft_end

def string_date(self, date):
""" Returns the date as a string """
return date.strftime("%4Y-%m-%d %H:%M:%S")

def get_string_dict(self):
""" Returns a dictionary with the start, end and soft_end as strings """
return {
"start": self.string_date(self._start),
"soft_end": self.string_date(self._soft_end),
"end": self.string_date(self._end)
}

def string_max(self):
""" Returns the max date as a string """
return self.string_date(self.max)

def string_min(self):
""" Returns the min date as a string """
return self.string_date(self.min)
Loading
Loading