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

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

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

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

Я думал, что, используя JavaScriptDateTimeConverter, JSON.NET сможет выяснить, как десериализовать эти временные метки unix (мс мкс с эпохи). Как это сделать проще всего?

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

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


person mpen    schedule 14.11.2013    source источник
comment
Это чрезвычайно важный вопрос, поскольку время unix - единственное представление, полностью устойчивое к недостаткам JS в отношении часовых поясов.   -  person Peter Wone    schedule 14.11.2013


Ответы (6)


Я немного очистил решение Криса и реализовал 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);
    }
}
person mpen    schedule 14.11.2013
comment
Хорошая уборка. Вы также можете поместить поле _epoch static, чтобы немного увеличить потребление памяти. - person Charles HETIER; 18.11.2014
comment
Отредактированный код для обработки DateTime? AKA Nullable DateTime. DateTimeConverterBase Может преобразовывать как DateTime, так и DateTime ?. - person Jess; 23.06.2015
comment
Спасибо, извините! Так же, как еще одно примечание для всех, кто его поймает - OP запросил преобразователь из unix MICRO секунд в C # Datetime. Я по глупости предположил, что с 1970 года время эпохи всегда выражалось в микросекундах, но обычно оно выражается в секундах (en.wikipedia .org / wiki / Unix_time), поэтому, когда я использовал этот конвертер, все было не так, что меня на мгновение смутило ^ _ ^ - person Ryan; 27.01.2016
comment
@ Райан В самом деле. PHP и большинство других языков используют секунды с эпохи, JavaScript использует миллисекунды для эпохи, но по какой-то причине Firefox решил хранить свои закладки как микросекунды с эпохи. - person mpen; 28.01.2016
comment
@mpen - Мои данные поступают в виде строки, представляющей длинный (из Instagram). Будет ли Convert.ToInt64 лучше, чем (long) reader.Value? В остальном работает отлично. - person alex; 15.01.2017
comment
@alex Эээ ... мой C # теперь немного поджил. Я думаю, вам нужно преобразовать его, а не приводить, если он исходит из строки. - person mpen; 15.01.2017
comment
Это работает, я изменил возвращаемое значение ReadJson на _epoch.AddMilliseconds ((long) reader.Value); для сериализации длинных значений, которые поступают из отметки времени Java - person Juan; 10.12.2018
comment
@Juan Если вам нужны миллисекунды вместо микросекунд, вы также должны удалить + "000" в WriteJson - person mpen; 11.12.2018
comment
Если кому-то еще интересно, почему это не работает, убедитесь, что вы используете то же пространство имен Newtonsoft.Json для аннотации [JsonConverter] в классе данных. - person Zoltán Barics; 27.07.2020
comment
@ ZoltánBarics Совершенно уверен, что я не использовал для этого NewtonSoft; должно быть System.Text.Json.Serialization.JsonConverter - person mpen; 28.07.2020

Вы можете создать собственный конвертер 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;
person Cris    schedule 14.11.2013
comment
Похоже, это работает. Для чего тогда используются атрибуты? - person mpen; 14.11.2013
comment
ItemConverterType предназначен для преобразования элементов в массив, попробуйте дважды аннотировать свойство с помощью JsonProperty и JsonConverter, например [JsonProperty (PropertyName = dateAdded)] [JsonConverter (typeof (JavaScriptDateTimeConverter))] - person Cris; 14.11.2013
comment
Двойная аннотация не требуется; [JsonConverter] - это атрибут, который я искал. Спасибо! (Но [JsonProperty] тоже неплохо, потому что теперь я могу переименовать свои свойства) - person mpen; 14.11.2013
comment
Использовал Convert.ToString (reader.Value) вместо собственного приведения объекта (string) reader.Value, чтобы заставить это работать для меня. - person Darth Jon; 22.08.2014
comment
Я попробовал ваш ответ, но я также хочу использовать настройку для игнорирования нулевых значений, как я могу использовать оба? когда я пытаюсь, как показано ниже, у меня есть недопустимые аргументы JsonConvert.DeserializeObject(restResponse.Content, typeof(MyClass), new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }, new MyDateTimeConverter()); - person Neo; 18.09.2015
comment
Какую именно ошибку вы здесь получаете? Это во время выполнения? - person Cris; 18.09.2015
comment
@DarthJon: var t = Convert.ToInt64 (reader.Value); избегает всех обходных путей - person TomB; 16.09.2020

Есть встроенный способ конвертировать временную метку unix в DateTime без необходимости писать свой собственный класс:

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

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

Аннотация в ответе Cris неверна, поскольку JavaScriptDateTimeConverter предназначен для формата Date(52231943), а не для временной метки Unix из OP.

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

person Aoki Metal    schedule 26.02.2018
comment
Мой вопрос касается микросекунд с эпохи, а не обычных секунд. Не думаю, что UnixDateTimeConverter подходит для этого, не так ли? - person mpen; 26.02.2018
comment
Обратите внимание, что UnixDateTimeConverter - это JsonConvert v.11 +. Если вы застряли на версии 10.x или ниже, вам придется использовать другие предлагаемые решения :) - person Frederik Struck-Schøning; 24.04.2018

Я хотел добавить решение для ядра .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;}
person Bryan Halterman    schedule 14.09.2020
comment
DateTime.UnixEpoch новый? Это выглядит немного чище. Спасибо! - person mpen; 16.09.2020
comment
@mpen, он доступен в .net standard 2.1, .net core 2.1 и выше docs.microsoft.com/en-us/dotnet/api/ - person Bryan Halterman; 17.09.2020

Хммм ... хитрость в том, что конструктор 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 методе.

person Noctis    schedule 14.11.2013

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

В моей 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



person redar ismail    schedule 27.06.2019