处理系统语音崩溃问题 ValueError: time data '٢٠٢٥-١١-٢٧ ٠٠:٥٢:١٦' does not match format '%Y-%m-%d %H:%M:%S'#199
处理系统语音崩溃问题 ValueError: time data '٢٠٢٥-١١-٢٧ ٠٠:٥٢:١٦' does not match format '%Y-%m-%d %H:%M:%S'#199bxvip wants to merge 1 commit intoopenatx:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes a ValueError that occurs when parsing app installation timestamps on Android devices configured with non-Latin numeral systems (e.g., Arabic, Persian, Thai). The issue arose because the app_info method expected timestamps in ASCII digits but received them in localized digit formats.
- Adds a digit normalization function to convert non-ASCII digits from 10 different language numeral systems to ASCII digits
- Wraps date parsing in try-except blocks with fallback to Unix epoch (1970-01-01) on parsing failures
- Applies normalization before parsing
firstInstallTimeandlastUpdateTimefields
| ) | ||
| try: | ||
| first_install_time = (datetime.datetime.strptime(_deal_number_cover(m.group(1)), "%Y-%m-%d %H:%M:%S") if m else None) | ||
| except (ValueError, TypeError) as e: |
There was a problem hiding this comment.
The exception variable e is caught but never used. Either remove the variable (use bare except clauses like except (ValueError, TypeError):) or log the exception for debugging purposes to help diagnose parsing issues.
| try: | ||
| last_update_time = (datetime.datetime.strptime(_deal_number_cover(m.group(1)), "%Y-%m-%d %H:%M:%S") if m else None) | ||
| except (ValueError, TypeError) as e: | ||
| last_update_time = datetime.datetime(1970, 1, 1) |
There was a problem hiding this comment.
Silently falling back to Unix epoch (1970-01-01) when date parsing fails may mask underlying issues and lead to incorrect app metadata. Consider logging the error or providing a more meaningful default value like None, which would be consistent with the original intent (returning None when regex doesn't match). The AppInfo dataclass expects datetime.datetime for these fields, so the type contract may need adjustment if None is preferred.
| ) | ||
| try: | ||
| last_update_time = (datetime.datetime.strptime(_deal_number_cover(m.group(1)), "%Y-%m-%d %H:%M:%S") if m else None) | ||
| except (ValueError, TypeError) as e: |
There was a problem hiding this comment.
The exception variable e is caught but never used. Either remove the variable (use bare except clauses like except (ValueError, TypeError):) or log the exception for debugging purposes to help diagnose parsing issues.
| def _deal_number_cover(text): | ||
| """转换任何语言的数字为英文数字""" | ||
| if not text: | ||
| return text | ||
|
|
||
| # 所有数字映射字典 | ||
| DIGIT_MAPS = { | ||
| 'arabic': dict(zip('٠١٢٣٤٥٦٧٨٩', '0123456789')), # 阿拉伯语 | ||
| 'persian': dict(zip('۰۱۲۳۴۵۶۷۸۹', '0123456789')), # 波斯语 乌尔都文 | ||
| 'hindi': dict(zip('०१२३४५६७८९', '0123456789')), # 印地文 尼泊尔文 马拉地文 | ||
| 'bengali': dict(zip('০১২৩৪๕৬৭৮৯', '0123456789')), # 孟加拉文 | ||
| 'thai': dict(zip('๐๑๒๓๔๕๖๗๘๙', '0123456789')), # 泰语 | ||
| 'burmese': dict(zip('၀၁၂၃၄၅၆၇၈၉', '0123456789')), # 缅甸文 | ||
| 'khmer': dict(zip('០១២៣៤៥៦៧៨៩', '0123456789')), # 高棉文 | ||
| 'lao': dict(zip('໐໑໒໓໔໕໖໗໘໙', '0123456789')), # 老挝文 | ||
| 'tamil': dict(zip('௦௧௨௩௪௫௬௭௮௯', '0123456789')), # 泰米尔文 | ||
| 'gujarati': dict(zip('૦૧૨૩૪૫૬૭૮૯', '0123456789')), # 古吉拉特文 | ||
| } | ||
|
|
||
| result = text | ||
| for lang, digit_map in DIGIT_MAPS.items(): | ||
| for native_digit, english_digit in digit_map.items(): | ||
| result = result.replace(native_digit, english_digit) | ||
| return result | ||
| time_regex = r"[-\d]+\s+[:\d]+" | ||
| m = re.compile(f"firstInstallTime=({time_regex})").search(output) | ||
| first_install_time = ( | ||
| datetime.datetime.strptime(m.group(1), "%Y-%m-%d %H:%M:%S") if m else None | ||
| ) | ||
| try: | ||
| first_install_time = (datetime.datetime.strptime(_deal_number_cover(m.group(1)), "%Y-%m-%d %H:%M:%S") if m else None) | ||
| except (ValueError, TypeError) as e: | ||
| first_install_time = datetime.datetime(1970, 1, 1) | ||
|
|
||
| m = re.compile(f"lastUpdateTime=({time_regex})").search(output) | ||
| last_update_time = ( | ||
| datetime.datetime.strptime(m.group(1).strip(), "%Y-%m-%d %H:%M:%S") | ||
| if m | ||
| else None | ||
| ) | ||
| try: | ||
| last_update_time = (datetime.datetime.strptime(_deal_number_cover(m.group(1)), "%Y-%m-%d %H:%M:%S") if m else None) | ||
| except (ValueError, TypeError) as e: | ||
| last_update_time = datetime.datetime(1970, 1, 1) |
There was a problem hiding this comment.
The app_info method now has digit normalization and error handling logic for date parsing, but lacks test coverage. Since the repository has comprehensive unit tests (in tests/test_adb_shell.py), consider adding a test case that verifies the function correctly handles non-ASCII digits (e.g., Arabic numerals) and gracefully handles parsing failures.
| result = text | ||
| for lang, digit_map in DIGIT_MAPS.items(): | ||
| for native_digit, english_digit in digit_map.items(): | ||
| result = result.replace(native_digit, english_digit) |
There was a problem hiding this comment.
The nested function unnecessarily iterates through all 10 digit mappings for every language, even after a replacement is made. Consider using a single comprehensive mapping dictionary that combines all languages, or optimize the function to stop processing after finding matches. For example, you could create a translation table once and use str.translate() which is more efficient than multiple replace() calls.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| pkgflags = pkgflags.split() | ||
|
|
||
|
|
||
| def _deal_number_cover(text): |
There was a problem hiding this comment.
The function name _deal_number_cover is unclear and doesn't follow Python naming conventions. Consider renaming it to _normalize_digits or _convert_digits_to_ascii which better describes its purpose of converting non-ASCII digits to ASCII digits.
| DIGIT_MAPS = { | ||
| 'arabic': dict(zip('٠١٢٣٤٥٦٧٨٩', '0123456789')), # 阿拉伯语 | ||
| 'persian': dict(zip('۰۱۲۳۴۵۶۷۸۹', '0123456789')), # 波斯语 乌尔都文 | ||
| 'hindi': dict(zip('०१२३४५६७८९', '0123456789')), # 印地文 尼泊尔文 马拉地文 | ||
| 'bengali': dict(zip('০১২৩৪๕৬৭৮৯', '0123456789')), # 孟加拉文 | ||
| 'thai': dict(zip('๐๑๒๓๔๕๖๗๘๙', '0123456789')), # 泰语 | ||
| 'burmese': dict(zip('၀၁၂၃၄၅၆၇၈၉', '0123456789')), # 缅甸文 | ||
| 'khmer': dict(zip('០១២៣៤៥៦៧៨៩', '0123456789')), # 高棉文 | ||
| 'lao': dict(zip('໐໑໒໓໔໕໖໗໘໙', '0123456789')), # 老挝文 | ||
| 'tamil': dict(zip('௦௧௨௩௪௫௬௭௮௯', '0123456789')), # 泰米尔文 | ||
| 'gujarati': dict(zip('૦૧૨૩૪૫૬૭૮૯', '0123456789')), # 古吉拉特文 | ||
| } |
There was a problem hiding this comment.
Defining DIGIT_MAPS as a constant inside the function on every call is inefficient. This dictionary should be defined as a module-level constant or class attribute to avoid recreating it each time the function is called. Move this dictionary outside the function scope.
| try: | ||
| first_install_time = (datetime.datetime.strptime(_deal_number_cover(m.group(1)), "%Y-%m-%d %H:%M:%S") if m else None) | ||
| except (ValueError, TypeError) as e: | ||
| first_install_time = datetime.datetime(1970, 1, 1) |
There was a problem hiding this comment.
Silently falling back to Unix epoch (1970-01-01) when date parsing fails may mask underlying issues and lead to incorrect app metadata. Consider logging the error or providing a more meaningful default value like None, which would be consistent with the original intent (returning None when regex doesn't match). The AppInfo dataclass expects datetime.datetime for these fields, so the type contract may need adjustment if None is preferred.
No description provided.