ccfddl/ccf-deadlines · error · ValueError

无效的时区格式

Error message

无效的时区格式: {tz_str}

What it means

get_timezone in extensions/cli/ccfddl/convert_to_ical.py converts a timezone string into a Python datetime.timezone. It only accepts the literals 'AoE' (UTC-12) and 'UTC', plus strings matching UTC[+-]<1-2 digits> (e.g. UTC+8, UTC-05). Any other string fails the regex and raises ValueError("无效的时区格式: {tz_str}").

Solutions

  1. Rewrite the timezone string to the accepted format, e.g. 'Asia/Shanghai' → 'UTC+8', keeping the exact capitalization 'UTC' and no minutes.
  2. Use the literal 'AoE' for anywhere-on-earth deadlines or 'UTC' for zero offset instead of 'UTC+0'/'GMT'.
  3. Preprocess input data to normalize abbreviations/IANA names to UTC±h before calling the converter.
  4. Extend get_timezone's regex to also accept minute offsets (UTC±HH:MM) or use zoneinfo.ZoneInfo for IANA names.

Example fix

// before
get_timezone("Asia/Shanghai")  # ValueError: 无效的时区格式: Asia/Shanghai
// after
get_timezone("UTC+8")  # timezone(timedelta(hours=8))
Defensive patterns

Strategy: validation

Validate before calling

import re
def is_supported_tz(tz_str):
    return tz_str in ("AoE", "UTC") or re.match(r"UTC([+-])(\d{1,2})$", tz_str) is not None

assert is_supported_tz(tz_str), f"unsupported timezone: {tz_str}"

Try / catch

try:
    tz = get_timezone(tz_str)
except ValueError as e:
    logging.error("skipping entry with bad timezone: %s", e)
    tz = timezone.utc  # or skip the entry

Prevention

When it happens

Trigger: Calling convert_to_ical (or get_timezone directly) with a deadline timezone string that is not 'AoE', 'UTC', or 'UTC±H' — e.g. 'Asia/Shanghai', 'GMT+8', 'UTC+8:30', 'utc+8' (case-sensitive), 'PST', or an empty string.

Common situations: Feeding the converter ICS/config data whose TZID or timezone field uses IANA zone names or abbreviations instead of the UTC±h offset convention this CLI expects; hand-editing a config and writing lowercase 'utc+8'; timezones with minute offsets like UTC+5:30 which the \d{1,2} regex rejects.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of ccfddl/ccf-deadlines@dedf5e76ab (2026-09-11). Data as JSON: /api/errors/f3929f377394abf9. Report an issue: GitHub.

Appendix: source

Thrown at extensions/cli/ccfddl/convert_to_ical.py:30

    with open(path, encoding="utf-8") as f:
        types = yaml.safe_load(f)
    if types is None:
        return {}
    SUB_MAPPING = {}
    for types_data in types:
        SUB_MAPPING[types_data["sub"]] = types_data["name"]
    return SUB_MAPPING


def get_timezone(tz_str: str) -> timezone:
    """将时区字符串转换为datetime.timezone对象"""
    if tz_str == "AoE":
        return timezone(timedelta(hours=-12))
    if tz_str == "UTC":
        return timezone.utc
    match = re.match(r"UTC([+-])(\d{1,2})$", tz_str)
    if not match:
        raise ValueError(f"无效的时区格式: {tz_str}")
    sign, hours = match.groups()
    offset = int(hours) if sign == "+" else -int(hours)
    return timezone(timedelta(hours=offset))


def create_vtimezone(tz: timezone) -> Timezone:
    """创建VTIMEZONE组件"""
    tz_offset = tz.utcoffset(datetime.now())
    offset_hours = tz_offset.total_seconds() // 3600
    tzid = f"UTC{offset_hours:+03.0f}:00"

    vtz = Timezone()
    vtz.add("TZID", tzid)

    std = TimezoneStandard()
    std.add("DTSTART", datetime(1970, 1, 1))
    std.add("TZOFFSETFROM", timedelta(hours=offset_hours))
    std.add("TZOFFSETTO", timedelta(hours=offset_hours))

View on GitHub (pinned to dedf5e76ab)