|
7 | 7 | import yaml |
8 | 8 | import shutil |
9 | 9 | from jinja2 import Environment, FileSystemLoader |
10 | | -from zoneinfo import ZoneInfo |
11 | 10 | import logging |
12 | 11 | import sys |
13 | 12 |
|
| 13 | +# Try ZoneInfo from stdlib, then from backports, else fall back to naive time |
| 14 | +try: |
| 15 | + from zoneinfo import ZoneInfo # type: ignore |
| 16 | +except Exception: # pragma: no cover - fallback for Python < 3.9 |
| 17 | + try: |
| 18 | + from backports.zoneinfo import ZoneInfo # type: ignore |
| 19 | + except Exception: # Last resort: define a stub |
| 20 | + ZoneInfo = None # type: ignore |
| 21 | + |
14 | 22 | logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") |
15 | 23 | logger = logging.getLogger(__name__) |
16 | 24 |
|
@@ -44,6 +52,16 @@ def assign_variant( |
44 | 52 | return 0 |
45 | 53 |
|
46 | 54 |
|
| 55 | +def _now_msk(): |
| 56 | + """Return current datetime in MSK if tz support is available, else local time.""" |
| 57 | + try: |
| 58 | + if ZoneInfo is not None: |
| 59 | + return datetime.now(ZoneInfo("Europe/Moscow")) |
| 60 | + except Exception: |
| 61 | + pass |
| 62 | + return datetime.now() |
| 63 | + |
| 64 | + |
47 | 65 | def _read_tasks_type(task_dir: Path) -> str | None: |
48 | 66 | """Read tasks_type from settings.json in the task directory (if present).""" |
49 | 67 | settings_path = task_dir / "settings.json" |
@@ -561,7 +579,6 @@ def main(): |
561 | 579 |
|
562 | 580 | # Helper: compute evenly spaced dates for current semester (MSK) |
563 | 581 | from datetime import date, timedelta |
564 | | - from zoneinfo import ZoneInfo as _ZoneInfo |
565 | 582 | import calendar |
566 | 583 |
|
567 | 584 | def _abbr(day: date) -> str: |
@@ -616,15 +633,21 @@ def _evenly_spaced_dates(n: int, start: date, end: date) -> list[date]: |
616 | 633 | return res |
617 | 634 |
|
618 | 635 | def _compute_display_deadlines_threads(order: list[str]) -> dict[str, date]: |
619 | | - # Threads = Spring semester |
620 | | - today = datetime.now(_ZoneInfo("Europe/Moscow")).date() |
| 636 | + # Threads = Spring semester (prefer MSK; fallback to local time) |
| 637 | + try: |
| 638 | + today = _now_msk().date() |
| 639 | + except Exception: |
| 640 | + today = datetime.now().date() |
621 | 641 | s, e = _spring_bounds(today) |
622 | 642 | ds = _evenly_spaced_dates(len(order), s, e) |
623 | 643 | return {t: d for t, d in zip(order, ds)} |
624 | 644 |
|
625 | 645 | def _compute_display_deadlines_processes(n_items: int) -> list[date]: |
626 | | - # Processes = Autumn semester |
627 | | - today = datetime.now(_ZoneInfo("Europe/Moscow")).date() |
| 646 | + # Processes = Autumn semester (prefer MSK; fallback to local time) |
| 647 | + try: |
| 648 | + today = _now_msk().date() |
| 649 | + except Exception: |
| 650 | + today = datetime.now().date() |
628 | 651 | s, e = _autumn_bounds(today) |
629 | 652 | ds = _evenly_spaced_dates(n_items, s, e) |
630 | 653 | return ds |
@@ -889,9 +912,7 @@ def _build_cell(dir_name: str, ttype: str): |
889 | 912 | output_path.mkdir(parents=True, exist_ok=True) |
890 | 913 |
|
891 | 914 | # Render tables |
892 | | - generated_msk = datetime.now(ZoneInfo("Europe/Moscow")).strftime( |
893 | | - "%Y-%m-%d %H:%M:%S" |
894 | | - ) |
| 915 | + generated_msk = _now_msk().strftime("%Y-%m-%d %H:%M:%S") |
895 | 916 | table_template = env.get_template("index.html.j2") |
896 | 917 | threads_vmax = int((cfg.get("threads", {}) or {}).get("variants_max", 1)) |
897 | 918 | # Build display deadlines (use file values if present, fill missing with auto) |
|
0 commit comments