diff --git a/src/Intercom.Tests/Converters/ClassConverters/DateTimeJsonConverterTest.cs b/src/Intercom.Tests/Converters/ClassConverters/DateTimeJsonConverterTest.cs new file mode 100644 index 0000000..4d808f4 --- /dev/null +++ b/src/Intercom.Tests/Converters/ClassConverters/DateTimeJsonConverterTest.cs @@ -0,0 +1,117 @@ +using System; +using System.IO; + +using Intercom.Test; +using Newtonsoft.Json; +using NUnit.Framework; + +using Intercom.Converters.ClassConverters; + +namespace Intercom.Tests.Converters.ClassConverters +{ + [TestFixture] + public class DateTimeOffsetJsonConverterTest : TestBase + { + private const string _DateCreatedISO = "1989-04-16T00:15:00Z"; + private const string _DateCreatedUnix = "608688900"; + private const string _Null = "null"; + + private readonly DateTimeOffsetJsonConverter _converter; + + public DateTimeOffsetJsonConverterTest() + { + _converter = new DateTimeOffsetJsonConverter(); + } + + [TestCase(_DateCreatedUnix)] + public void ReadJson_ForDateTimeOffset_ReturnsValidDateTimeOffset(string json) + { + using (StringReader stringReader = new StringReader(json)) + using (JsonReader jsonReader = new JsonTextReader(stringReader)) + { + DateTimeOffset dateCreated = (DateTimeOffset)_converter.ReadJson(jsonReader, typeof(DateTimeOffset), null, null); + + Assert.AreEqual(_ParseUtcDateString(_DateCreatedISO), dateCreated); + } + } + + [TestCase(_DateCreatedISO)] + public void WriteJsonForDateTimeOffset_ForcesUtc(string input) + { + DateTimeOffset inputDate; + + try + { + inputDate = _ParseUtcDateString(input); + } + + catch (Exception ex) + { + throw new Exception("Failed to properly parse the test input.", ex); + } + + inputDate.ToOffset(TimeSpan.FromHours(-5)); + + using (StringWriter stringWriter = new StringWriter()) + using (JsonWriter jsonWriter = new JsonTextWriter(stringWriter)) + { + _converter.WriteJson(jsonWriter, inputDate, null); + + Assert.AreEqual(_DateCreatedUnix, stringWriter.GetStringBuilder().ToString()); + } + } + + [TestCase(_Null)] + public void ReadJson_ForNullableDateTimeOffset_ReturnsNull(string json) + { + using (StringReader stringReader = new StringReader(json)) + using (JsonReader jsonReader = new JsonTextReader(stringReader)) + { + DateTimeOffset? dateCreated = (DateTimeOffset?)_converter.ReadJson(jsonReader, typeof(DateTimeOffset?), null, null); + + Assert.AreEqual(null, dateCreated); + } + } + + [TestCase(null)] + public void WriteJsonForNullableDateTimeOffset_ReturnsNull(DateTimeOffset? input) + { + using (StringWriter stringWriter = new StringWriter()) + using (JsonWriter jsonWriter = new JsonTextWriter(stringWriter)) + { + _converter.WriteJson(jsonWriter, input, null); + + Assert.AreEqual(_Null, stringWriter.GetStringBuilder().ToString()); + } + } + + [TestCase(_DateCreatedISO)] + public void WriteJson_ForDateTimeOffset_ReturnsValidUnixTimestamp(string input) + { + DateTimeOffset inputDate; + + try + { + inputDate = _ParseUtcDateString(input); + } + + catch (Exception ex) + { + throw new Exception("Failed to properly parse the test input.", ex); + } + + using (StringWriter stringWriter = new StringWriter()) + using (JsonWriter jsonWriter = new JsonTextWriter(stringWriter)) + { + _converter.WriteJson(jsonWriter, inputDate, null); + + Assert.AreEqual(_DateCreatedUnix, stringWriter.GetStringBuilder().ToString()); + } + } + + private DateTimeOffset _ParseUtcDateString(string input) + { + return DateTimeOffset.Parse(input); + } + } +} diff --git a/src/Intercom/Clients/UsersClient.cs b/src/Intercom/Clients/UsersClient.cs index 17702c7..41b0066 100644 --- a/src/Intercom/Clients/UsersClient.cs +++ b/src/Intercom/Clients/UsersClient.cs @@ -240,36 +240,26 @@ public User Delete(String id) return result.Result; } - public User UpdateLastSeenAt(String id, long timestamp) + public User UpdateLastSeenAt(String id, DateTimeOffset timestamp) { if (String.IsNullOrEmpty(id)) { throw new ArgumentNullException(nameof(id)); } - if (timestamp <= 0) - { - throw new ArgumentException("'timestamp' argument should be bigger than zero."); - } - ClientResponse result = null; String body = JsonConvert.SerializeObject(new { id = id, last_request_at = timestamp }); result = Post(body); return result.Result; } - public User UpdateLastSeenAt(User user, long timestamp) + public User UpdateLastSeenAt(User user, DateTimeOffset timestamp) { if (user == null) { throw new ArgumentNullException(nameof(user)); } - if (timestamp <= 0) - { - throw new ArgumentException("'timestamp' argument should be bigger than zero."); - } - String body = String.Empty; if (!String.IsNullOrEmpty(user.id)) diff --git a/src/Intercom/Converters/ClassConverters/DateTimeJsonConverter.cs b/src/Intercom/Converters/ClassConverters/DateTimeJsonConverter.cs new file mode 100644 index 0000000..90c21f6 --- /dev/null +++ b/src/Intercom/Converters/ClassConverters/DateTimeJsonConverter.cs @@ -0,0 +1,65 @@ +using System; + +using Newtonsoft.Json; + +namespace Intercom.Converters.ClassConverters +{ + public class DateTimeOffsetJsonConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return (objectType.Equals(typeof(DateTimeOffset)) || objectType.Equals(typeof(DateTimeOffset?))); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + object value = reader.Value; + + if (value == null) + { + return null; + } + + long unixTimestamp; + + try + { + unixTimestamp = Convert.ToInt64(value); + } + + catch (InvalidCastException ex) + { + throw new FormatException("Dates must be represented as UNIX timestamps in JSON.", ex); + } + + DateTimeOffset unixEpoch = _GetUnixEpoch(); + DateTimeOffset result = unixEpoch.AddSeconds(unixTimestamp); + + return result; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null) + { + writer.WriteRawValue("null"); + + return; + } + + DateTimeOffset unixEpoch = _GetUnixEpoch(); + DateTimeOffset dateTimeOffset = (DateTimeOffset)value; + + dateTimeOffset = dateTimeOffset.ToOffset(TimeSpan.Zero); + + long unixTimestamp = Convert.ToInt64((dateTimeOffset - unixEpoch).TotalSeconds); + + writer.WriteRawValue(unixTimestamp.ToString()); + } + + private DateTimeOffset _GetUnixEpoch() + { + return new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero); + } + } +} diff --git a/src/Intercom/Data/Company.cs b/src/Intercom/Data/Company.cs index 4e0f982..43b4559 100644 --- a/src/Intercom/Data/Company.cs +++ b/src/Intercom/Data/Company.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Newtonsoft.Json; using Intercom.Converters.AttributeConverters; +using Intercom.Converters.ClassConverters; namespace Intercom.Data { @@ -12,10 +13,14 @@ public class Company : Model public string name { get; set; } public Plan plan { get; set; } public string company_id { get; set; } - public long? remote_created_at { get; set; } - public long? created_at { get; set; } - public long? updated_at { get; set; } - public long? last_request_at { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset? remote_created_at { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset? created_at { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset? updated_at { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset? last_request_at { get; set; } public int? monthly_spend { get; set; } public int? session_count { get; set; } public int? user_count { get; set; } diff --git a/src/Intercom/Data/Conversation.cs b/src/Intercom/Data/Conversation.cs index 1551df2..c6da62e 100644 --- a/src/Intercom/Data/Conversation.cs +++ b/src/Intercom/Data/Conversation.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Intercom.Clients; using Intercom.Converters.AttributeConverters; +using Intercom.Converters.ClassConverters; using Intercom.Core; using Intercom.Data; using Intercom.Exceptions; @@ -11,10 +12,14 @@ namespace Intercom.Data { public class Conversation : Model { - public long created_at { get; set; } - public long updated_at { get; set; } - public long? waiting_since { get; set; } - public long? snoozed_until { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset created_at { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset updated_at { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset? waiting_since { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset? snoozed_until { get; set; } public Assignee assignee { get; set; } public User user { get; set; } public bool open { get; set; } diff --git a/src/Intercom/Data/ConversationPart.cs b/src/Intercom/Data/ConversationPart.cs index 0dc894b..23fcbcc 100644 --- a/src/Intercom/Data/ConversationPart.cs +++ b/src/Intercom/Data/ConversationPart.cs @@ -4,6 +4,8 @@ using Intercom.Clients; using Intercom.Exceptions; using System.Collections.Generic; +using Newtonsoft.Json; +using Intercom.Converters.ClassConverters; namespace Intercom.Data { @@ -11,9 +13,12 @@ public class ConversationPart : Model { public string part_type { get; set; } public string body { get; set; } - public long created_at { get; set; } - public long updated_at { get; set; } - public long notified_at { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset created_at { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset updated_at { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset notified_at { get; set; } public Assignee assigned_to { get; set; } public Author author { get; set; } public List attachments { get; set; } diff --git a/src/Intercom/Data/Event.cs b/src/Intercom/Data/Event.cs index eae74cb..0198680 100644 --- a/src/Intercom/Data/Event.cs +++ b/src/Intercom/Data/Event.cs @@ -6,13 +6,15 @@ using System.Collections.Generic; using Newtonsoft.Json; using Intercom.Converters.AttributeConverters; +using Intercom.Converters.ClassConverters; namespace Intercom.Data { public class Event : Model { public string event_name { get; set; } - public long? created_at { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset? created_at { get; set; } public string user_id { get; set; } public string email { get; set; } diff --git a/src/Intercom/Data/Note.cs b/src/Intercom/Data/Note.cs index cb72523..7e2b2dd 100644 --- a/src/Intercom/Data/Note.cs +++ b/src/Intercom/Data/Note.cs @@ -3,12 +3,15 @@ using Intercom.Data; using Intercom.Clients; using Intercom.Exceptions; +using Newtonsoft.Json; +using Intercom.Converters.ClassConverters; namespace Intercom.Data { public class Note : Model { - public long? created_at { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset? created_at { get; set; } public string body { get; set; } public Admin author { get; set; } public User user { get; set; } diff --git a/src/Intercom/Data/Segment.cs b/src/Intercom/Data/Segment.cs index 3fa1e88..6e65db1 100644 --- a/src/Intercom/Data/Segment.cs +++ b/src/Intercom/Data/Segment.cs @@ -6,15 +6,18 @@ using Intercom.Clients; using Intercom.Exceptions; - +using Newtonsoft.Json; +using Intercom.Converters.ClassConverters; namespace Intercom.Data { public class Segment : Model { public string name { get; set; } - public long created_at { get; set; } - public long updated_at { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset created_at { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset updated_at { get; set; } public Segment () { diff --git a/src/Intercom/Data/User.cs b/src/Intercom/Data/User.cs index cbffb37..32630a4 100644 --- a/src/Intercom/Data/User.cs +++ b/src/Intercom/Data/User.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Newtonsoft.Json; using Intercom.Converters.AttributeConverters; +using Intercom.Converters.ClassConverters; namespace Intercom.Data { @@ -12,12 +13,16 @@ public class User : Model public string email { get; set; } public string phone { get; set; } public string name { get; set; } - public long? updated_at { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset? updated_at { get; set; } public string last_seen_ip { get; set; } public bool? unsubscribed_from_emails { get; set; } - public long? last_request_at { get; set; } - public long? signed_up_at { get; set; } - public long? created_at { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset? last_request_at { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset? signed_up_at { get; set; } + [JsonConverter(typeof(DateTimeOffsetJsonConverter))] + public DateTimeOffset? created_at { get; set; } public int? session_count { get; set; } public bool? new_session { get; set; } public string user_agent_data { get; set; }