/// <summary> /// Writes the JSON representation of the object. /// </summary> /// <param name="writer">The <see cref="JsonWriter"/> to write to.</param> /// <param name="value">The value.</param> /// <param name="serializer">The calling serializer.</param> public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value == null) { writer.WriteNull(); return; } switch (value) { case DateTime dt: writer.WriteValue(UnixTimeUtils.ToInt64(dt, Format)); break; case DateTimeOffset dto: writer.WriteValue(UnixTimeUtils.ToInt64(dto, Format)); break; case EssentialsDateTime edt: writer.WriteValue(UnixTimeUtils.ToInt64(edt.DateTime, Format)); break; case EssentialsTime et: writer.WriteValue(UnixTimeUtils.ToInt64(et, Format)); break; default: throw new ArgumentException($"Unknown type {value.GetType()}", nameof(value)); } }
private ulong GetCurrentUnixTimestampMilliseconds() { var time = UnixTimeUtils.GetCurrentUnixTimestampMilliseconds(); var newTime = (ulong)((long)time + TimeDifference); return(newTime); }
public void FromMillisecondsString_NormalTime() { TimeZoneInfo romance = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time"); DateTimeOffset resultUtc = UnixTimeUtils.FromMilliseconds("1643886000123"); DateTimeOffset resultDenmark = TimeZoneInfo.ConvertTime(resultUtc, romance); Assert.AreEqual("2022-02-03T11:00:00.123+00:00", resultUtc.ToString("yyyy-MM-ddTHH:mm:ss.fffK")); Assert.AreEqual("2022-02-03T12:00:00.123+01:00", resultDenmark.ToString("yyyy-MM-ddTHH:mm:ss.fffK")); }
public void FromSecondsDouble_NormalTime() { TimeZoneInfo romance = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time"); DateTimeOffset resultUtc = UnixTimeUtils.FromSeconds(1643886000d); DateTimeOffset resultDenmark = TimeZoneInfo.ConvertTime(resultUtc, romance); Assert.AreEqual("2022-02-03T11:00:00+00:00", resultUtc.ToString(Iso8601Constants.DateTimeSeconds)); Assert.AreEqual("2022-02-03T12:00:00+01:00", resultDenmark.ToString(Iso8601Constants.DateTimeSeconds)); }
public void FromSecondsInt64_SummerTime() { TimeZoneInfo romance = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time"); DateTimeOffset resultUtc = UnixTimeUtils.FromSeconds(1629182100L); DateTimeOffset resultDenmark = TimeZoneInfo.ConvertTime(resultUtc, romance); Assert.AreEqual("2021-08-17T06:35:00+00:00", resultUtc.ToString(Iso8601Constants.DateTimeSeconds)); Assert.AreEqual("2021-08-17T08:35:00+02:00", resultDenmark.ToString(Iso8601Constants.DateTimeSeconds)); }
public void FromMillisecondsString_SummerTime() { TimeZoneInfo romance = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time"); DateTimeOffset resultUtc = UnixTimeUtils.FromMilliseconds("1629182100123"); DateTimeOffset resultDenmark = TimeZoneInfo.ConvertTime(resultUtc, romance); Assert.AreEqual("2021-08-17T06:35:00.123+00:00", resultUtc.ToString("yyyy-MM-ddTHH:mm:ss.fffK")); Assert.AreEqual("2021-08-17T08:35:00.123+02:00", resultDenmark.ToString("yyyy-MM-ddTHH:mm:ss.fffK")); }
public void ToMillisecondsDateTime() { // Must be UTC as we really can't rely on anything else when unit testing DateTime DateTime dt = new DateTime(2022, 2, 3, 11, 0, 0, 123, DateTimeKind.Utc); // Cast to long as we don't really care about any decimals long result = (long)UnixTimeUtils.ToMilliseconds(dt); // Do we have a match? Assert.AreEqual(1643886000123, result); }
public void ToSecondsDateTime() { // Must be UTC as we really can't rely on anything else when unit testing DateTime DateTime dt = new DateTime(2022, 2, 3, 11, 0, 0, DateTimeKind.Utc); // Cast to int as we don't really care about the milliseconds here int result = (int)UnixTimeUtils.ToSeconds(dt); // Do we have a match? Assert.AreEqual(1643886000, result); }
public void CurrentMilliseconds() { double actual = UnixTimeUtils.CurrentMilliseconds; // Since Unix time is calculated back to UTC, using "DateTime.Now" and "DateTime.UtcNow" should give the // same result double expected1 = UnixTimeUtils.ToMilliseconds(DateTime.Now); double expected2 = UnixTimeUtils.ToMilliseconds(DateTime.UtcNow); Assert.AreEqual(expected2, actual); Assert.AreEqual(expected2, expected1); }
public void ToMillisecondsDateTimeOffset() { // Initialize to different DateTimeOffset representing the same point in time, but with different offsets DateTimeOffset dto1 = new DateTimeOffset(2022, 2, 3, 11, 0, 0, 123, TimeSpan.Zero); DateTimeOffset dto2 = new DateTimeOffset(2022, 2, 3, 12, 0, 0, 123, TimeSpan.FromHours(1)); // Cast to long as we don't really care about any decimals long result1 = (long)UnixTimeUtils.ToMilliseconds(dto1); long result2 = (long)UnixTimeUtils.ToMilliseconds(dto2); // Do we have a match? Assert.AreEqual(1643886000123, result1); Assert.AreEqual(1643886000123, result2); }
public ulong GetNextMessageId() { // Documentation says that message id should be unixtime * 2^32. // But the real world calculations in other client software looking very weird. // Have no idea how it is actually calculated. ulong messageId = UnixTimeUtils.GetCurrentUnixTimestampMilliseconds(); messageId = (messageId * 4294967 + (messageId * 296 / 1000)) & X4Mask; if (messageId <= _lastMessageId) { messageId = _lastMessageId + 4; } _lastMessageId = messageId; return(messageId); }
public void GetDateTimeOffsetFromUnixTime_Int64() { var samples = new[] { new { Timestamp = 1483384930L, Date = new DateTimeOffset(2017, 1, 2, 19, 22, 10, TimeSpan.Zero) }, new { Timestamp = 1483384930L, Date = new DateTimeOffset(2017, 1, 2, 18, 22, 10, TimeSpan.FromHours(-1)) }, new { Timestamp = 1483384930L, Date = new DateTimeOffset(2017, 1, 2, 20, 22, 10, TimeSpan.FromHours(+1)) } }; int n = 1; foreach (var sample in samples) { Assert.AreEqual(sample.Date, TimeHelper.GetDateTimeOffsetFromUnixTime(sample.Timestamp), $"\n\n#{n}. {sample.Timestamp} (TimeHelper)"); Assert.AreEqual(sample.Date, TimeUtils.GetDateTimeOffsetFromUnixTime(sample.Timestamp), $"\n\n#{n}. {sample.Timestamp} (TimeUtils)"); Assert.AreEqual(sample.Date, UnixTimeUtils.FromSeconds(sample.Timestamp), $"\n\n#{n}. {sample.Timestamp} (UnixTimeUtils)"); n++; } }
internal static object ToFormat(DateTimeOffset value, TimeFormat format) { switch (format) { case TimeFormat.Iso8601: return(Iso8601Utils.ToString(value)); case TimeFormat.Rfc822: return(Rfc822Utils.ToString(value)); case TimeFormat.Rfc2822: return(Rfc2822Utils.ToString(value)); case TimeFormat.UnixTime: return((long)UnixTimeUtils.ToSeconds(value)); default: throw new ArgumentException("Unsupported format " + format, nameof(format)); } }
/// <summary> /// Reads the JSON representation of the object. /// </summary> /// <param name="reader">The <see cref="JsonReader"/> to read from.</param> /// <param name="objectType">Type of the object.</param> /// <param name="existingValue">The existing value of object being read.</param> /// <param name="serializer">The calling serializer.</param> /// <returns>The object value.</returns> public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { string type; if (objectType.Name == "Nullable`1") { // Just return NULL if (reader.TokenType == JsonToken.Null) { return(null); } // Get the name of the generic type type = objectType.GenericTypeArguments[0].FullName; } else { // Get the name of the type type = objectType.FullName; } // Read/parse the timestamp from the reader double timestamp = reader.TokenType switch { JsonToken.Integer => (long)reader.Value, JsonToken.Float => (double)reader.Value, JsonToken.String => double.Parse((string)reader.Value, CultureInfo.InvariantCulture), _ => throw new JsonSerializationException($"Unsupported value {reader.Value}"), }; // Convert to the correct type based on the value type of the property return(type switch { "System.DateTime" => UnixTimeUtils.ToDateTime(timestamp, Format), "System.DateTimeOffset" => UnixTimeUtils.ToDateTimeOffset(timestamp, Format), "Skybrud.Essentials.Time.EssentialsDateTime" => new EssentialsDateTime(UnixTimeUtils.ToDateTime(timestamp, Format)), "Skybrud.Essentials.Time.EssentialsTime" => new EssentialsTime(UnixTimeUtils.ToDateTimeOffset(timestamp, Format)), "Skybrud.Essentials.Time.EssentialsDate" => new EssentialsDate(UnixTimeUtils.ToDateTimeOffset(timestamp, Format)), _ => throw new JsonSerializationException($"Unsupported type {objectType.FullName}") });
public void CurrentSeconds() { double actual = UnixTimeUtils.CurrentSeconds; // Since Unix time is calculated back to UTC, using "DateTime.Now" and "DateTime.UtcNow" should give the // same result double expected1 = UnixTimeUtils.ToSeconds(DateTime.Now); double expected2 = UnixTimeUtils.ToSeconds(DateTime.UtcNow); Assert.AreEqual(expected2, expected1); // Comparing against current time is always tricky. If this is the first test to run, there may be a ~100 ms // difference between the actual and expected timestamps. If other tests have run first, the two timestamps // so far seem to be the same ¯\_(ツ)_/¯ // Since the important part we want to test here is that the two timestamps are based on the same timezone, // we can confirm this by checking that "delta" is below 1 second. double delta1 = Math.Abs(actual - expected1); double delta2 = Math.Abs(actual - expected2); Assert.IsTrue(delta1 < 1, "#1"); Assert.IsTrue(delta2 < 1, "#2"); }
protected override async Task HandleInternalAsync(IMessage responseMessage) { #region Notice of Ignored Error Message /* In certain cases, a server may notify a client that its incoming message was ignored for whatever reason. * Note that such a notification cannot be generated unless a message is correctly decoded by the server. * * bad_msg_notification#a7eff811 bad_msg_id:long bad_msg_seqno:int error_code:int = BadMsgNotification; * bad_server_salt#edab447b bad_msg_id:long bad_msg_seqno:int error_code:int new_server_salt:long = BadMsgNotification; * * Here, error_code can also take on the following values: * * 16: msg_id too low (most likely, client time is wrong; it would be worthwhile to synchronize it using msg_id notifications * and re-send the original message with the “correct” msg_id or wrap it in a container with a new msg_id if the original * message had waited too long on the client to be transmitted) * 17: msg_id too high (similar to the previous case, the client time has to be synchronized, and the message re-sent with the correct msg_id) * 18: incorrect two lower order msg_id bits (the server expects client message msg_id to be divisible by 4) * 19: container msg_id is the same as msg_id of a previously received message (this must never happen) * 20: message too old, and it cannot be verified whether the server has received a message with this msg_id or not * 32: msg_seqno too low (the server has already received a message with a lower msg_id but with either a higher or an equal and odd seqno) * 33: msg_seqno too high (similarly, there is a message with a higher msg_id but with either a lower or an equal and odd seqno) * 34: an even msg_seqno expected (irrelevant message), but odd received * 35: odd msg_seqno expected (relevant message), but even received * 48: incorrect server salt (in this case, the bad_server_salt response is received with the correct salt, and the message is to be re-sent with it) * 64: invalid container. * The intention is that error_code values are grouped (error_code >> 4): for example, the codes 0x40 - 0x4f correspond to errors in container decomposition. * * Notifications of an ignored message do not require acknowledgment (i.e., are irrelevant). * * Important: if server_salt has changed on the server or if client time is incorrect, any query will result in a notification in the above format. * The client must check that it has, in fact, recently sent a message with the specified msg_id, and if that is the case, * update its time correction value (the difference between the client’s and the server’s clocks) and the server salt based on msg_id * and the server_salt notification, so as to use these to (re)send future messages. * In the meantime, the original message (the one that caused the error message to be returned) must also be re-sent with a better msg_id and/or server_salt. * * In addition, the client can update the server_salt value used to send messages to the server, * based on the values of RPC responses or containers carrying an RPC response, * provided that this RPC response is actually a match for the query sent recently. * (If there is doubt, it is best not to update since there is risk of a replay attack). * * https://core.telegram.org/mtproto/service_messages_about_messages#notice-of-ignored-error-message */ #endregion var response = (IBadMsgNotification)responseMessage.Body; var errorCode = (ErrorCode)response.ErrorCode; Console.WriteLine(string.Format("Bad message notification received with error code: {0} ({1}).", response.ErrorCode, errorCode)); Console.WriteLine("Searching for bad message in the request manager..."); IRequest request = _requestsManager.Get(response.BadMsgId); if (request == null) { Console.WriteLine(string.Format("Bad message (MsgId: 0x{0:X}) was NOT found. Ignored.", response.BadMsgId)); return; } if (request.Message.Seqno != response.BadMsgSeqno) { Console.WriteLine( string.Format( "Bad message (MsgId: 0x{0:X}) was found, but message sequence number is not the same ({1}) as server expected ({2}). Ignored.", response.BadMsgId, request.Message.Seqno, response.BadMsgSeqno)); return; } var badServerSalt = response as BadServerSalt; if (badServerSalt != null) { if (errorCode != ErrorCode.IncorrectServerSalt) { Console.WriteLine(string.Format("Error code must be '{0}' for a BadServerSalt notification, but found '{1}'.", ErrorCode.IncorrectServerSalt, errorCode)); } Console.WriteLine( string.Format( "Bad server salt was in outgoing message (MsgId = 0x{0:X}, Seqno = {1}). Error code = {2}.", badServerSalt.BadMsgId, badServerSalt.BadMsgSeqno, errorCode)); Console.WriteLine("Setting new salt."); _connection.UpdateSalt(badServerSalt.NewServerSalt); Console.WriteLine("Resending bad message with the new salt."); await request.SendAsync(); return; } var badMsgNotification = response as BadMsgNotification; if (badMsgNotification != null) { // TODO: implement. switch (errorCode) { case ErrorCode.MsgIdIsTooSmall: case ErrorCode.MsgIdIsTooBig: case ErrorCode.MsgIdDuplicate: case ErrorCode.MsgTooOld: case ErrorCode.MsgIdBadTwoLowBytes: ulong time = (ulong)(responseMessage.MsgId / 4294967296.0 * 1000); ulong currentTime = UnixTimeUtils.GetCurrentUnixTimestampMilliseconds(); var timeDelta = ((long)time - (long)currentTime); _connection.MessageIdGenerator.TimeDifference = timeDelta; Disa.Framework.Utils.DebugPrint("Time drift set to: " + _connection.MessageIdGenerator.TimeDifference); var newMessageId = _connection.MessageIdGenerator.GetNextMessageId(); var oldMessageId = request.Message.MsgId; var message = new Message(newMessageId, request.Message.Seqno, request.Message.Body); request.UpdateMessage(message); _requestsManager.Change(newMessageId, oldMessageId); await request.SendAsync(); break; case ErrorCode.MsgSeqnoIsTooLow: case ErrorCode.MsgSeqnoIsTooBig: case ErrorCode.MsgSeqnoNotEven: case ErrorCode.MsgSeqnoNotOdd: case ErrorCode.IncorrectServerSalt: case ErrorCode.InvalidContainer: throw new NotImplementedException(); default: throw new ArgumentOutOfRangeException(); } } }
public static int GetUnixTimeFromDateTimeOffset(DateTimeOffset date) { return((int)UnixTimeUtils.ToSeconds(date)); }
public static double GetUnixTimeFromDateTimeOffsetAsDouble(DateTimeOffset date) { return(UnixTimeUtils.ToSeconds(date)); }
public static DateTimeOffset GetDateTimeOffsetFromUnixTime(int timestamp) { return(UnixTimeUtils.FromSeconds(timestamp)); }
public static DateTime GetDateTimeFromUnixTime(string timestamp) { return(UnixTimeUtils.FromSeconds(timestamp).UtcDateTime.ToUniversalTime()); }