68

JSON

{
   "title":"Mozilla Firefox",
   "id":24,
   "parent":2,
   "dateAdded":1356753810000000,
   "lastModified":1356753810000000,
   "type":"text/x-moz-place-container",
   "children":[]
}

C#

class Bookmark
{
    public string title;
    public string id;
    [JsonProperty(ItemConverterType = typeof(JavaScriptDateTimeConverter))]
    public DateTime dateAdded;
    [JsonProperty(ItemConverterType = typeof(JavaScriptDateTimeConverter))]
    public DateTime lastModified;
    public string type;
    public string root;
    public long parent;
    public List<Bookmark> children;
}

private static void Main(string[] args)
{
    var json = File.ReadAllText(@"T:/bookmarks-2013-11-13.json");
    var bookmarks = JsonConvert.DeserializeObject<Bookmark>(json);
}

I get an exception when I try running this,

Additional information: Error reading date. Unexpected token: Integer. Path 'dateAdded'

I thought by using the JavaScriptDateTimeConverter, JSON.NET could figure out how to deserialize those unix timestamps (ms μs since epoch). What's the easiest way to do this?

Having trouble finding documentation on the converters... it probably wouldn't be too hard to write one myself if necessary.

Edit: Those are actually microseconds, not milliseconds.

Community
  • 1
  • 1
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • 4
    This is an extremely worthwhile question since unix times are the only representation that is completely resistant to the shortcomings of JS with respect to time zones. – Peter Wone Nov 14 '13 at 08:17

7 Answers7

74

I cleaned up Cris's solution a tad and implemented WriteJson:

class Bookmark
{
    public string title;
    public long id;
    [JsonConverter(typeof(MicrosecondEpochConverter))]
    public DateTime dateAdded;
    [JsonConverter(typeof(MicrosecondEpochConverter))]
    public DateTime lastModified;
    public string type;
    public string root;
    public long parent;
    public List<Bookmark> children;
    public string uri;

    public override string ToString()
    {
        return string.Format("{0} - {1}", title, uri);
    }
}

public class MicrosecondEpochConverter : DateTimeConverterBase
{
    private static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteRawValue(((DateTime)value - _epoch).TotalMilliseconds + "000");
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.Value == null) { return null; }
        return _epoch.AddMilliseconds((long)reader.Value / 1000d);
    }
}

internal class Program
{

    private static void Main(string[] args)
    {
        var jsonString = File.ReadAllText(@"T:/bookmarks-2013-11-13.json");
        var rootMark = JsonConvert.DeserializeObject<Bookmark>(jsonString);
        var ret = JsonConvert.SerializeObject(rootMark);
    }
}
Community
  • 1
  • 1
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • 1
    Nice clean up. You also could put the _epoch field static to enhance a bit memory consumption – Charles HETIER Nov 18 '14 at 13:27
  • Edited code to handle `DateTime?` AKA Nullable DateTime. DateTimeConverterBase "CanConvert" both DateTime and DateTime?. – Jess Jun 23 '15 at 20:10
  • 1
    Thanks sorry! Just as a further note to anyone who is caught out by it - the OP asked for a converter from unix MICRO seconds to C# Datetime. I foolishly assumed that epoch time was always given as microseconds since 1970 but it is usually expressed in seconds (https://en.wikipedia.org/wiki/Unix_time) so when I used this converter everything was wrong which confused me for a moment ^_^ – Ryan Jan 27 '16 at 09:55
  • @Ryan Indeed. PHP and most other languages use seconds since epoch, JavaScript uses milliseconds to epoch, but for some reason Firefox decided to store their bookmarks as microseconds since epoch. – mpen Jan 27 '16 at 22:49
  • @mpen - My data is coming in as a string representing a long (from Instagram). Would a Convert.ToInt64 be better than (long)reader.Value? Otherwise works perfectly. – alex Jan 14 '17 at 21:33
  • @alex Uhh... my C# is a bit rusty now. I think you do have to convert it instead of cast it if it's coming from a string. – mpen Jan 14 '17 at 22:00
  • 1
    It works, I changed return value on ReadJson to _epoch.AddMilliseconds((long)reader.Value); to Serialize long values that come from Java Timestamp – Juan Dec 10 '18 at 16:02
  • @Juan If you want milliseconds instead of microseconds you should remove the `+ "000"` in the `WriteJson` too – mpen Dec 11 '18 at 06:19
  • If anyone else wondering why doesn't it work, make sure that you use the same Newtonsoft.Json namespace for the [JsonConverter] annotation in the data class. – Zoltán Barics Jul 27 '20 at 11:42
  • @ZoltánBarics Pretty sure I wasn't using NewtonSoft for this; should be `System.Text.Json.Serialization.JsonConverter` – mpen Jul 28 '20 at 05:24
  • Just a note: In my case the date was coming from MongoDB. This is already in milliseconds, so I didn't have to divide by 1000. Thanks! – Daniel Lobo Jul 26 '22 at 09:01
63

There's a built-in way to convert from unix timestamp to DateTime without having to write your own class:

[JsonConverter(typeof(UnixDateTimeConverter))]
public DateTime lastModified;

https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Converters_UnixDateTimeConverter.htm

The annotation in Cris' answer is not correct, as JavaScriptDateTimeConverter is for Date(52231943) format rather than the Unix timestamp from the OP.

I realize this question is a few years old now so it's highly likely this class has been added since this question has been asked, but this may help anyone who comes across the same issue.

Aoki Metal
  • 639
  • 7
  • 3
  • 6
    My question is about microseconds since epoch though, not the usual seconds. Don't think `UnixDateTimeConverter` works for that, does it? – mpen Feb 26 '18 at 05:48
  • Note that `UnixDateTimeConverter` is JsonConvert v.11+. If you are stuck with 10.x or below, you will have to go with some of the other proposed solutions :) – Frederik Struck-Schøning Apr 24 '18 at 13:27
44

You can create a custom DateTime converter

  var bookmarks = JsonConvert.DeserializeObject<Bookmark>(json,
                                                      new MyDateTimeConverter());

public class MyDateTimeConverter : Newtonsoft.Json.JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(DateTime);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var t = long.Parse((string)reader.Value);
        return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(t);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

another approach is to double annotate the class member,

[JsonProperty(PropertyName="dateAdded")] 
[JsonConverter(typeof(JavaScriptDateTimeConverter))]
public DateTime dateAdded;
Cris
  • 12,799
  • 5
  • 35
  • 50
  • This looks like it works. What are the attributes used for then? – mpen Nov 14 '13 at 07:20
  • 1
    ItemConverterType is for converting items in an array,try Double-annotating the property with JsonProperty and JsonConverter like [JsonProperty(PropertyName="dateAdded")] [JsonConverter(typeof(JavaScriptDateTimeConverter))] – Cris Nov 14 '13 at 07:22
  • 2
    Double-annotation isn't necessary; `[JsonConverter]` is the attribute I was looking for. Thank you! (But `[JsonProperty]` is nice as well, because now I can rename my properties) – mpen Nov 14 '13 at 07:46
  • 1
    Used a Convert.ToString(reader.Value) instead of native object cast (string)reader.Value to get this to work for me. – Darth Jon Aug 22 '14 at 19:05
  • I tried your answer but I also want to use setting to ignore null values how can I use both ? as when i try like below i got some invalid arguments are there `JsonConvert.DeserializeObject(restResponse.Content, typeof(MyClass), new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }, new MyDateTimeConverter());` – Neo Sep 18 '15 at 09:46
  • Exactly which error you are getting here?is it run-time? – Cris Sep 18 '15 at 10:49
  • @DarthJon: var t = Convert.ToInt64(reader.Value); avoids all the string detours – TomB Sep 16 '20 at 09:41
11

I wanted to add a solution for .Net core and System.Text.Json based on the accepted answer. Verified in 3.1 at time of writing.

public class UnixDateTimeConverter : JsonConverter<DateTime>
{
   public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
   {
       return DateTime.UnixEpoch.AddSeconds(reader.GetInt64());
   }

   public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
   {
       writer.WriteStringValue((value - DateTime.UnixEpoch).TotalMilliseconds + "000");
   }
}

You could then apply this using a data annotation

[JsonConverter(typeof(UnixDateTimeConverter))]
public DateTime MyDateTime {get;set;}
Bryan Halterman
  • 315
  • 4
  • 10
  • Is `DateTime.UnixEpoch` new? This looks a little cleaner. Thanks! – mpen Sep 16 '20 at 02:58
  • @mpen, It is available in .net standard 2.1, .net core 2.1 and up https://learn.microsoft.com/en-us/dotnet/api/system.datetime.unixepoch?view=netcore-3.1 – Bryan Halterman Sep 17 '20 at 12:21
2

Hmmm ...the tricky bit is that DateTime constructor doesn't accept a timestamp as an argument.

Here's a solution from this article: How to convert a Unix timestamp to DateTime and vice versa?

public static DateTime UnixTimeStampToDateTime( double unixTimeStamp )
{
    // Unix timestamp is seconds past epoch
    System.DateTime dtDateTime = new DateTime(1970,1,1,0,0,0,0);
    dtDateTime = dtDateTime.AddSeconds( unixTimeStamp ).ToLocalTime();
    return dtDateTime;
}

You'll have to call this, probably best by using default constructor in your class, or having another property that will return this in it's get method.

Community
  • 1
  • 1
Noctis
  • 11,507
  • 3
  • 43
  • 82
1

Similar to Bryan's answer, here's a System.Text.Json deserialize variant that supports a few more options..

  • Unix time defined in seconds OR milliseconds. Assumption: seconds < year 9999 (DateTime max) and milliseconds > 1978.
  • Nullable DateTime
public class UnixToNullableDateTimeConverter : JsonConverter<DateTime?>
{
    public override bool HandleNull => true;
    public bool? IsFormatInSeconds { get; set; } = null;

    public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TryGetInt64(out var time))
        {
            // if 'IsFormatInSeconds' is unspecified, then deduce the correct type based on whether it can be represented in the allowed .net DateTime range
            if (IsFormatInSeconds == true || IsFormatInSeconds == null && time > _unixMinSeconds && time < _unixMaxSeconds)
                return DateTimeOffset.FromUnixTimeSeconds(time).LocalDateTime;
            return DateTimeOffset.FromUnixTimeMilliseconds(time).LocalDateTime;
        }

        return null;
    }

    public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options) => throw new NotSupportedException();

    private static readonly long _unixMinSeconds = DateTimeOffset.MinValue.ToUnixTimeSeconds() - DateTimeOffset.UnixEpoch.ToUnixTimeSeconds(); // -62_135_596_800
    private static readonly long _unixMaxSeconds = DateTimeOffset.MaxValue.ToUnixTimeSeconds() - DateTimeOffset.UnixEpoch.ToUnixTimeSeconds(); // 253_402_300_799
}
stoj
  • 1,116
  • 1
  • 14
  • 25
-1

That is how I did it and it worked

In my ViewModel I have a Public property of Type DateTime

Public DateTime TimeToBeShown {get; set;}

In My Model, I have the public property of type Long which gets the date from API as a JSON Format.

Public long DateThatIsComingAsJsonFormat {get; set;}
     var dateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
     TimeToBeShown=dateTime.AddSeconds(somethingfromloop.CreatedTime).ToLocalTime();
Bind to TimeToBeShown in Xaml



redar ismail
  • 163
  • 2
  • 13