9

How can I convert a date time format from JSON.Net such as:

/Date(1154970000000+0700)/

To ISO-?? format 2011-12-18T23:34:59Z

Preferably in either Python or Javascript.

Ive decided on the latter as its seems in the JS world the most widely used, humanly readable and naturally sortable. I'll store offsets on a per user basis.

If an implementation is again a bit much too ask, if someone can tell me the correct name for both formats I might have more luck in understanding how to convert.

RobG
  • 142,382
  • 31
  • 172
  • 209

4 Answers4

3

[Replacement answer]

Here is a Python 2.x version. Only the print statements in the testing section need to be changed for Python 3.x.

As far as I can ascertain by googling: The main component is milliseconds since 1970-01-01. It can be negative. A + sign is NOT expected for positive numbers. This can be followed by an OPTIONAL offset from UTC, which consists of 5 characters: an mandatory sign (+ or -), 2 digits for hours, and 2 digits for minutes. All of the above is preceded by "/Date(" and followed by ")/".

This answer provides a function to convert the JSON.NET string to a Python datetime.datetime (timestamp) object, and 2 functions to return ISO format truncated to seconds and milliseconds respectively.

Script:

# /Date(1154970000000+0700)/
# 0123456............7654321
# without timezone:
# /Date(1154970000000)/
# 0123456............21
# dodgy case
# /Date(-1234)/
# 3210987654321
import datetime

def json_date_as_datetime(jd):
    sign = jd[-7]
    if sign not in '-+' or len(jd) == 13:
        millisecs = int(jd[6:-2])
    else:
        millisecs = int(jd[6:-7])
        hh = int(jd[-7:-4])
        mm = int(jd[-4:-2])
        if sign == '-': mm = -mm
        millisecs += (hh * 60 + mm) * 60000
    return datetime.datetime(1970, 1, 1) \
        + datetime.timedelta(microseconds=millisecs * 1000)

def datetime_as_iso(dt):
    return dt.strftime("%Y-%m-%dT%H:%M:%SZ") # truncates

def datetime_as_iso_ms(dt): # with millisecs as fraction
    return dt.strftime("%Y-%m-%dT%H:%M:%S.%%03dZ") \
        % (dt.microsecond // 1000) # truncate    

if __name__ == "__main__":

    tests = """\
    /Date(1154970000000+0700)/
    /Date(-1234)/
    /Date(1000+0200)/
    /Date(0+0000)/
    /Date(0)/
    /Date(0-0700)/
    /Date(0-0730)/
    /Date(0-0030)/
    /Date(-1577923200000+0000)/
    /Date(1)/
    /Date(499)/
    /Date(500)/
    /Date(501)/
    /Date(999)/
    /Date(1000)/
    /Date(-1)/
    """.splitlines()

    for test in tests:
        test = test.strip()
        if not test: continue
        d = json_date_as_datetime(test)
        print datetime_as_iso_ms(d), test

Output:

2006-08-08T00:00:00.000Z /Date(1154970000000+0700)/
1969-12-31T23:59:58.766Z /Date(-1234)/
1970-01-01T02:00:01.000Z /Date(1000+0200)/
1970-01-01T00:00:00.000Z /Date(0+0000)/
1970-01-01T00:00:00.000Z /Date(0)/
1969-12-31T17:00:00.000Z /Date(0-0700)/
1969-12-31T16:30:00.000Z /Date(0-0730)/
1969-12-31T23:30:00.000Z /Date(0-0030)/
1920-01-01T00:00:00.000Z /Date(-1577923200000+0000)/
1970-01-01T00:00:00.001Z /Date(1)/
1970-01-01T00:00:00.499Z /Date(499)/
1970-01-01T00:00:00.500Z /Date(500)/
1970-01-01T00:00:00.501Z /Date(501)/
1970-01-01T00:00:00.999Z /Date(999)/
1970-01-01T00:00:01.000Z /Date(1000)/
1969-12-31T23:59:59.999Z /Date(-1)/
John Machin
  • 81,303
  • 11
  • 141
  • 189
  • @mattcodes: Updated with better style, fewer bugs and more tests :-) – John Machin Apr 26 '11 at 09:00
  • json_date_as_datetime("/Date(1428145200000+1200)/") → datetime.date(2015, 4, 4, 23, 0). Not what the JS version above does... – Matthew Schinckel Apr 13 '15 at 06:47
  • ...because some of the test cases are wrong (1, 3, 6, 7 and 8). Unless I've misunderstood this (not good) date format, the milliseconds part is the UTC offset from the epoch, and the optional offset shifts it to whatever timezone it's representing. All tests here with an offset are incorrectly including it in the UTC timestamp. – Electric Head Sep 11 '19 at 20:05
1

Returns timezone aware datetimes and provides correct output for John Machin's test cases and "/Date(1428145200000+1200)/"

Python >3.3 compatible. For 2.7, use pytz.utc instead of datetime.timezone.utc.

from datetime import datetime, timedelta, timezone
import re


def jsondate(jsondate, tzinfo=timezone.utc):
    """Converts an ASP.NET json date: "/DATE(x)/" to tz-aware datetime object."""

    regex = (
        r"/Date\("
        r"(?P<milleseconds>[\-]?\d+)"
        r"(?P<offset>"
        r"(?P<offset_sign>[\+\-])"
        r"(?P<offset_hours>[01][0-9]|2[0-3])"
        r"(?P<offset_mins>[0-5][0-9])"
        r")?\)/"
    )
    try:
        parts = re.match(regex, jsondate).groupdict()
    except (AttributeError, TypeError):
        raise ValueError("Unsupported ASP.NET JSON Date Format: %s" % jsondate)

    since_epoch = timedelta(microseconds=1000 * int(parts['milleseconds']))
    if parts.get('offset'):
        since_epoch += timedelta(
            hours=int("%s%s" % (parts['offset_sign'], parts['offset_hours'])),
            minutes=int("%s%s" % (parts['offset_sign'], parts['offset_mins']))
        )
    return datetime(year=1970, month=1, day=1, tzinfo=tzinfo) + since_epoch
notpeter
  • 1,046
  • 11
  • 16
1
jsonDate = "/Date(1154970000000+0700)/";

var strDate = parseInt(jsonDate.replace(/\/Date\(([-\d]+).*$/, "$1"));
var strHour = parseInt(jsonDate.replace(/.*\d([+-]\d\d).*$/, "$1"), 10);
var strMin = parseInt(jsonDate.replace(/.*\d([+-])\d\d(\d\d).*$/, "$1$2"), 10);

var date = new Date(strDate);
if (!isNaN(strHour)) date.setHours(date.getHours() + strHour);
if (!isNaN(strMin)) date.setMinutes(date.getMinutes() + strMin);

var out = date.toISOString();

And the function to convert to ISO:

var toISOString = Date.prototype.toISOString ?
    function(d){return d}:
    (function(){
        function t(i){return i<10?"0"+i:i};
        function h(i){return i.length<2?"00"+i:i.length<3?"0"+i:3<i.length?Math.round(i/Math.pow(10,i.length-3)):i};
        function toISOString(){
            return "".concat(
                this.getUTCFullYear(), "-",
                t(this.getUTCMonth() + 1), "-",
                t(this.getUTCDate()), "T",
                t(this.getUTCHours()), ":",
                t(this.getUTCMinutes()), ":",
                t(this.getUTCSeconds()), ".",
                h("" + this.getUTCMilliseconds()), "Z"
            );
        };
        return function(d){
            d.toISOString = toISOString;
            return d;
        }
    })();
ab_732
  • 3,639
  • 6
  • 45
  • 61
ariel
  • 15,620
  • 12
  • 61
  • 73
  • TypeError: Object Tue Aug 08 2006 00:00:00 GMT+0700 (ICT) has no method 'format' –  Apr 26 '11 at 06:10
  • My code was ignoring the "+0700", added code to add it to the hours/minutes of the converted date. Please check if this is the correct behaviour, if not, just ignore the time. – ariel Apr 26 '11 at 06:34
  • @ariel, @mattcodes: Those hard-coded substr numbers look a little bit suspect ... what about dates in 1970; are they padded with leading zeroes? What about dates *BEFORE* 1970 (like recording the date of birth of wrinklies like me)? – John Machin Apr 26 '11 at 09:05
  • @ariel: Ummm ... it's treating a negative timezone (-0700) the same as a positive one (+0700). – John Machin Apr 26 '11 at 12:32
  • thanks john, updated to allow negative numbers and negative time zone – ariel Apr 26 '11 at 21:28
  • @ariel: To reassure people who have extreme difficulty guessing what the code does and don't know how to run Javascript, how about publishing some test output? – John Machin Apr 27 '11 at 00:57
0

Here's a little class I wrote years ago to clean up this sort of invalid JSON that some .NET library generates:

class DotNETDecoder(simplejson.JSONDecoder):
    '''
    This is a decoder to convert .NET encoded JSON into python objects
    The motivation for this is the way .NET encodes dates.
    See:
    https://msdn.microsoft.com/en-us/library/bb299886.aspx#intro_to_json_topic2
    .NET encodes datetimes like this: "\/Date(628318530718)\/"
    '''

    def __init__(self, timezone, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.parse_string = self._date_parse_string(timezone)
        self.scan_once = py_make_scanner(self)

    @staticmethod
    def _date_parse_string(timezone):
        def _parse_string(string, idx, encoding, strict):
            obj = scanstring(string, idx, encoding, strict)
            if isinstance(obj[0], str):
                match = date_match.search(obj[0])
                if match:
                    return [dt.datetime.fromtimestamp(
                                int(match.group(1)) / 1000, timezone),
                            obj[1]]
            return obj
        return _parse_string

And a test case / example:

def test_can_decode_dotnet_json_dates():                                                                                                                                                                     
    jsonstr = '{"date": "Date(1330848000000)", "f": "b", "l": [], "i": 5}'                                                                                                                                   
    timezone = pytz.timezone('America/New_York')                                                                                                                                                             
    obj = json.loads(jsonstr, cls=DotNETDecoder, timezone=timezone)                                                                                                                                          
    assert obj['date'] == timezone.localize(dt.datetime(2012, 3, 4, 3, 0))                                                                                                                                   
    assert obj['f'] == "b"                                                                                                                                                                                   
    assert obj['i'] == 5                                                                                                                                                                                     
    assert obj['l'] == []
Stephen Fuhry
  • 12,624
  • 6
  • 56
  • 55