|
22 | 22 | import uuid |
23 | 23 | import os |
24 | 24 | import tldextract |
| 25 | +from zoneinfo import ZoneInfo |
25 | 26 |
|
26 | 27 | # Third-party imports |
27 | 28 | # ------------------- |
|
42 | 43 | # ------------------- |
43 | 44 | from rsptx.db.models import ( |
44 | 45 | AuthUserValidator, |
| 46 | + Courses, |
45 | 47 | CoursesValidator, |
46 | 48 | Lti1p3Conf, |
47 | 49 | Lti1p3User, |
@@ -461,7 +463,7 @@ async def launch(request: Request): |
461 | 463 | # make sure RS assignment is up to date (e.g. end date) |
462 | 464 | course_attributes = await fetch_all_course_attributes(course.id) |
463 | 465 | await update_rsassignment_from_lti( |
464 | | - rs_assign, assign_lineitem, course_attributes |
| 466 | + rs_assign, assign_lineitem, course_attributes, course |
465 | 467 | ) |
466 | 468 |
|
467 | 469 | # start redirect to assignment |
@@ -508,14 +510,34 @@ async def update_lti_assignment_record( |
508 | 510 |
|
509 | 511 |
|
510 | 512 | async def update_rsassignment_from_lti( |
511 | | - assign: AssignmentValidator, line_item: LineItem, course_attributes: dict |
| 513 | + assign: AssignmentValidator, |
| 514 | + line_item: LineItem, |
| 515 | + course_attributes: dict, |
| 516 | + course: Courses, |
512 | 517 | ) -> AssignmentValidator: |
513 | 518 | """ |
514 | 519 | Update a runestone assignment from LTI data. |
515 | 520 | """ |
516 | 521 | try: |
517 | 522 | lms_due_string = line_item.get_end_date_time() |
518 | | - lms_due = datetime.datetime.fromisoformat(lms_due_string) |
| 523 | + rslogger.info( |
| 524 | + f"LTI1p3 - Received {lms_due_string} for assignment {assign.name}" |
| 525 | + ) |
| 526 | + |
| 527 | + # Parse ISO datetime. Normalize trailing Z so fromisoformat can parse UTC. |
| 528 | + normalized_due_string = ( |
| 529 | + lms_due_string.replace("Z", "+00:00") |
| 530 | + if lms_due_string.endswith("Z") |
| 531 | + else lms_due_string |
| 532 | + ) |
| 533 | + lms_due = datetime.datetime.fromisoformat(normalized_due_string) |
| 534 | + |
| 535 | + # If LMS provided timezone info and course has a timezone, convert to course local time. |
| 536 | + if lms_due.tzinfo is not None and course.timezone: |
| 537 | + lms_due = lms_due.astimezone(ZoneInfo(course.timezone)) |
| 538 | + rslogger.info( |
| 539 | + f"LTI1p3 - Converted to {lms_due} in timezone {course.timezone}" |
| 540 | + ) |
519 | 541 | lms_due = lms_due.replace(tzinfo=None) |
520 | 542 | if ( |
521 | 543 | lms_due is not None |
@@ -1125,7 +1147,9 @@ async def assign_select(launch_id: str, request: Request, course=None): |
1125 | 1147 | ) |
1126 | 1148 | await ags.update_lineitem(line_item) |
1127 | 1149 | # update RS due date |
1128 | | - await update_rsassignment_from_lti(assign, line_item, course_attributes) |
| 1150 | + await update_rsassignment_from_lti( |
| 1151 | + assign, line_item, course_attributes, course |
| 1152 | + ) |
1129 | 1153 | deep_link = message_launch.get_deep_link() |
1130 | 1154 | response_html = deep_link.output_response_form(response_list) |
1131 | 1155 | response = HTMLResponse(content=response_html, status_code=200) |
|
0 commit comments