Skip to content

Instantly share code, notes, and snippets.

@AfroThundr3007730
Last active April 24, 2026 05:36
Show Gist options
  • Select an option

  • Save AfroThundr3007730/1ec8a10df9345b337c19363a8094ab04 to your computer and use it in GitHub Desktop.

Select an option

Save AfroThundr3007730/1ec8a10df9345b337c19363a8094ab04 to your computer and use it in GitHub Desktop.
Fixup JSON dates exported by .NET JavaScriptSerializer
#!/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