Как десериализовать метку времени Unix (мкс) в DateTime?

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);
}

Я получаю исключение, когда пытаюсь запустить это,

Дополнительная информация: Ошибка чтения даты. Неожиданный токен: Integer. Путь 'dateAdded'

Я думал, используя JavaScriptDateTimeConverterJSON.NET может выяснить, как десериализовать эти временные метки Unix (эпоха ms sinc). Какой самый простой способ сделать это?

Возникли проблемы с поиском документации по конвертерам... вероятно, было бы не сложно написать ее самостоятельно, если это необходимо.

Изменить: Это на самом деле микросекунды, а не миллисекунды.

7 ответов

Решение

Я немного очистил решение Криса и внедрил 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);
    }
}

Существует встроенный способ преобразования метки времени Unix в DateTime без необходимости писать собственный класс:

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

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

Аннотация в ответе Криса не верна, так как JavaScriptDateTimeConverter для Date(52231943) формат, а не метка времени Unix из OP.

Я понимаю, что этому вопросу уже несколько лет, поэтому весьма вероятно, что этот класс был добавлен после того, как этот вопрос был задан, но это может помочь любому, кто сталкивается с той же проблемой.

Вы можете создать собственный конвертер DateTime

  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();
    }
}

Другой подход заключается в двойном аннотировании члена класса,

[JsonProperty(PropertyName="dateAdded")] 
[JsonConverter(typeof(JavaScriptDateTimeConverter))]
public DateTime dateAdded;

Я хотел добавить решение для ядра.Net и System.Text.Jsonна основе принятого ответа.Проверено в версии 3.1 на момент написания.

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");
   }
}

Затем вы можете применить это, используя аннотацию данных

[JsonConverter(typeof(UnixDateTimeConverter))]
public DateTime MyDateTime {get;set;}

Хммм... хитрый момент в том, что конструктор DateTime не принимает метку времени в качестве аргумента.

Вот решение из этой статьи: Как преобразовать метку времени Unix в DateTime и наоборот?

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;
}

Вам придется вызывать это, вероятно, лучше всего, используя конструктор по умолчанию в вашем классе или имея другое свойство, которое будет возвращать это в свой класс. get метод.

Подобно ответу Брайана, вот System.Text.Jsonвариант десериализации, который поддерживает еще несколько параметров.

  • Время Unix определяется в секундах ИЛИ миллисекундах. Предположение: секунды < года 9999 (макс. DateTime) и миллисекунды > 1978.
  • Дата и время, допускающие значение NULL
      public class UnixToNullableDateTimeConverter : JsonConverter<DateTime?>
{
    public override bool HandleNull => true;

    public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        // conversions outside the unix min/max seconds range are assumed to be in milliseconds (conversions beyond this range in .net6 are rejected)
        if (reader.TryGetInt64(out var time))
            return time < _unixMinSeconds && time > _unixMaxSeconds ? DateTimeOffset.FromUnixTimeSeconds(time).LocalDateTime : 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
}

Вот как я это сделал, и это сработало

В моей ViewModel у меня есть свойство Public типа DateTime

Public DateTime TimeToBeShown {get; set;}

В моей модели у меня есть общедоступное свойство типа Long, которое получает дату из API в формате JSON.

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



Другие вопросы по тегам