Last active
April 24, 2026 05:36
-
-
Save AfroThundr3007730/1ec8a10df9345b337c19363a8094ab04 to your computer and use it in GitHub Desktop.
Fixup JSON dates exported by .NET JavaScriptSerializer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # SPDX-License-Identifier: GPL-3.0-or-later | |
| # For issues or updated versions of this script, browse to the following URL: | |
| # https://gist.github.com/AfroThundr3007730/1ec8a10df9345b337c19363a8094ab04 | |
| """ | |
| Fixup JSON dates exported by .NET JavaScriptSerializer | |
| Example input file: | |
| > $FormatEnumerationLimit = -1 | |
| > Get-ADUser -Filter * -Properties * | | |
| > ConvertTo-Json -Compress | Out-File AD_Users.json | |
| """ | |
| __author__ = "AfroThundr" | |
| __modified__ = "2026-04-23" | |
| __version__ = "0.3.2" | |
| from collections.abc import Callable | |
| from datetime import datetime, UTC | |
| from json import dump, load | |
| from os import stat | |
| from re import search | |
| from sys import argv, maxsize | |
| type NStr = str | None | |
| type JVal = str | int | float | bool | None | |
| type JDoc = dict[str, JVal | JDoc] | list[JVal | JDoc] | |
| type JData = JDoc | JVal | |
| type NodeFilter = Callable[[JData, NStr, NStr], JData] | |
| type NodeWalker = Callable[[JData], JData] | |
| def parse_netdate(obj: JData, _: NStr = None, regex: NStr = None) -> JData: | |
| """Reads .NET JSON timestamps and returns ISO-8601 timestamps""" | |
| regex = regex or r"\/Date\((-?\d+)\)\/" | |
| if isinstance(obj, str) and (match := search(regex, obj)): | |
| return datetime.fromtimestamp( | |
| int(match.groups()[0]) / 1000, UTC | |
| ).strftime("%FT%TZ") | |
| if isinstance(obj, int) and 18 <= len(str(obj)) <= 19: | |
| if obj == maxsize: | |
| return "Never" | |
| epoch1 = -int(datetime.strptime("0001", "%Y").strftime("%s")) | |
| epoch2 = -int(datetime.strptime("1601", "%Y").strftime("%s")) | |
| obj /= 10_000_000 | |
| obj = obj - epoch1 if obj > epoch1 else obj - epoch2 | |
| return datetime.fromtimestamp(obj, UTC).strftime("%FT%TZ") | |
| return obj | |
| def make_walker(func: NodeFilter) -> NodeWalker: | |
| """Create a function that walks an object and calls a method on members""" | |
| def _walk(node: JData, key: NStr = None) -> JData: | |
| """Walks an object and calls a method on members""" | |
| if isinstance(node, dict): | |
| return {k: _walk(v, k) for k, v in node.items()} | |
| if isinstance(node, list): | |
| return [_walk(v, key) for v in node] | |
| return func(node, key, None) | |
| return _walk | |
| if __name__ == "__main__": | |
| hook: NodeWalker = make_walker(parse_netdate) | |
| for entry in argv[1:]: | |
| if stat(entry).st_size == 0: | |
| print("Empty file:", entry) | |
| continue | |
| print("Processing:", entry) | |
| with ( | |
| open(entry, "r", encoding="utf-8") as f1, | |
| open(entry, "r+", encoding="utf-8") as f2, | |
| ): | |
| dump(load(f1, object_hook=hook), f2, ensure_ascii=False, indent=2) | |
| f2.truncate() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment