using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Globalization; namespace TradeIdeas.MiscSupport { /// /// This class has some routines for converting values between C# data types and the format required /// by the TI servers. /// public static class ServerFormats { /// /// The server always works with time in time_t format. Messages to and from the server are /// typically in that format. We have our own conversion that works better than any of /// the C# libraries for us. /// /// public static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); /// /// Takes a date in the current time zone (set in TI Pro) and converts it to UTC. ToUniversalTime() doesn't work /// since when the time zone in TI Pro is set to something other than local the date is incorrect. /// /// /// public static DateTime ToUtc(this DateTime dt) { return TimeZoneInfo.ConvertTimeToUtc(dt, ServerFormats.CurrentTimeZone); } public static DateTime? ToUtc(this DateTime? dt) { if (!dt.HasValue) return dt; return TimeZoneInfo.ConvertTimeToUtc(dt.Value, ServerFormats.CurrentTimeZone); } public static DateTime FromUtc(this DateTime dt) { return TimeZoneInfo.ConvertTimeFromUtc(dt, ServerFormats.CurrentTimeZone); } public static DateTime? FromUtc(this DateTime? dt) { if (!dt.HasValue) return dt; return TimeZoneInfo.ConvertTimeFromUtc(dt.Value, ServerFormats.CurrentTimeZone); } /// /// Convert from a DateTime object to a time_t value. /// /// The time to convert. This must be in /// The time in time_t format, or 0 if the input was null. public static long ToTimeT(DateTime? dt) { // Interesting. Converting the unix epoch to local time does not appear to work. It was off by an // hour. And just subtracting without converting did not work. It appears that the subtraction // operation ignored the timezone (or "kind") of the two parameters. if (dt == null) return 0; DateTime usableDT = new DateTime(dt.Value.Ticks, DateTimeKind.Unspecified); // ConvertTime() and ConverTimeToUtc() both assert that the kind property matches the // source time zone property. That's annoying and causes a lot of trouble. We say // that it's on the caller to make sure that the input is in the CurrentTimeZone. // We then ignore the DateTimeKind of the input, which is consistent with most // operations, including the - operator. TimeSpan diff = TimeZoneInfo.ConvertTimeToUtc(usableDT, CurrentTimeZone) - UnixEpoch; return (long)Math.Round(diff.TotalSeconds); } /// /// Convert from the time_t format to a C# DateTime object. /// /// The time in time_t format. /// The time, expressed in the . Or null if there is a problem. public static DateTime? FromTimeT(long timeT) { try { // Test conditions that may cause ArgumentOutOfRangeExceptions. if (timeT <= 0) return null; long testTicks = timeT * TimeSpan.TicksPerSecond; if (testTicks > TimeSpan.MaxValue.Ticks) return null; if (testTicks * TimeSpan.TicksPerSecond < TimeSpan.MinValue.Ticks) return null; if (testTicks <= 0) // Catch where the long multiplication returns a negative number because timeT is too big. return null; TimeSpan timeSpan = new TimeSpan(testTicks); long testDateTicks = UnixEpoch.Ticks + timeSpan.Ticks; if (testDateTicks > DateTime.MaxValue.Ticks) return null; if (testDateTicks < DateTime.MinValue.Ticks) return null; DateTime testDate = UnixEpoch + timeSpan; return TimeZoneInfo.ConvertTimeFromUtc(testDate, CurrentTimeZone); } catch { return null; } } /// /// Convert a time from the server into a DateTime object in the . /// /// This can return null for a number of reasons. The input could be complete invalid. /// The input could be empty. The input could be 0. /// /// Most of the time the server will send us values in the time_t format, and converted to /// a string. However, that is not the only valid format. Use this function to do the /// conversion, in case it is in another format. /// /// The input from the server. /// The time. public static DateTime? DecodeServerTime(string source) { // The preferred format for time is time_t. Over the wire it is usually converted // between an integer and a string in the obvious way. The other option is to send // the time in Eastern time. In that case time is sent the time as a mysql string. // We store it as if it were local time. That is to say that we store it in C# as // if we were in the eastern timezone. And we lie and say that it is local time. long asNumber; double asDouble; if (long.TryParse(source, out asNumber)) return FromTimeT(asNumber); else if (double.TryParse(source, out asDouble)) return FromTimeT((int)asDouble); // This is currently a placeholder. See the Delphi version of DecodeServerTime() // for more details. // TODO Add the second format. return null; } /// /// The standard dot net library will let you read but not set the current time zone. /// So we make our own variable. Everyone should use ServerFormats.TimeZoneInfo rather /// than TimeZoneInfo.Local or TimeZone.CurrentTimeZone. Most people only use the /// timezone indirectly, so that's not a huge problem. Use , , or /// to translate a time between "local" time and the server format. And use /// ServerFormats.Now rather than DateTime.Now. That should cover everything. /// static public TimeZoneInfo CurrentTimeZone = TimeZoneInfo.Local; /// /// Use this to get the current time in the same timezone as all of the data from the server. /// If you compare DateTime.Now to a time that this library creates, the results will make no /// sense. /// static public DateTime Now { get { return TimeZoneInfo.ConvertTime(DateTime.Now, CurrentTimeZone); } } /// /// Use this to get the current date in the same timezone as all of the data from the server. /// /// static public DateTime Today { get { DateTime saveNow = Now; return new DateTime(saveNow.Year, saveNow.Month, saveNow.Day); } } /// /// Use this to parse floating point numbers from the server. /// /// Floating point numbers are the most obvious case to fail if you use the wrong culture. Is /// "1,002" one thousand and two, or one and one five hundreth? When talking to the server we always want /// a period to mean the decimal point. We allow commas as number seperators, /// because they are used in a few places. /// /// A string from the server. /// The result of the conversion or 0 on failure. /// True if we were able to do the parsing. public static bool TryParse(string source, out double result) { return Double.TryParse(source, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result); } /// /// Use this to parse integers from the server. /// This is not as interesting as parsing a floating point number, but we still /// want to allow a comma as a number /// seperator. Sometimes the server will send things that way. Also, if the user /// types a number, this will allow us to parse it the same way that the server /// would. /// /// The input from the server. /// The result, or 0 if there was a problem. /// True if the parse succeeded. public static bool TryParse(string source, out int result) { return Int32.TryParse(source, NumberStyles.Integer | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result); } /// /// Use this to parse 64 bit integers from the server. /// This is not as interesting as parsing a floating point number, but we still /// want to allow a comma as a number /// seperator. Sometimes the server will send things that way. Also, if the user /// types a number, this will allow us to parse it the same way that the server /// would. /// /// The input from the server. /// The result, or 0 if there was a problem. /// True if the parse succeeded. public static bool TryParse(string source, out Int64 result) { return Int64.TryParse(source, NumberStyles.Integer | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result); } /// /// Convert a double into a string that the server will understand. /// /// The number to convert. /// The number as the server wants to see it. public static string ToString(double source) { // This one doesn't have to be used in a lot of places. You can send a double // as part of a message, and TalkWithServer.CreateMessageFromArray will call // this function. return source.ToString(CultureInfo.InvariantCulture); } /// /// Convert a double? into a string that the server will understand. /// If double? is null return empty string. /// /// The number to convert. /// The number as the server wants to see it. public static string ToString(double? source) { // This one doesn't have to be used in a lot of places. Currently used in // OddsMakerConfig when saving config settings. if (source == null) return ""; double sourceDouble = (double)source; return sourceDouble.ToString(CultureInfo.InvariantCulture); } /// /// Convert text with abreviations to a double. /// Abbreviatoins K (thousands), M (millions), and B (billions). /// /// The string user entered. /// The result, or 0 if there was a problem. /// True if the parse succeeded. public static bool TryParseWithAbreviations(string source, out double result) { string textValue = source.ToUpperInvariant(); // List of characters to exclude. char[] charSeparators = new char[] { ' ', 'K', 'M', 'B' }; string[] splitText; double value; result = 0; // We are going to separate the text value from the blank spaces and abbreviations the user enters. splitText = textValue.Split(charSeparators, StringSplitOptions.RemoveEmptyEntries); if (splitText.Length == 1) { double factor = FactorTextBoxText(textValue); if (double.TryParse(splitText[0], out value)) { result = value * factor; return true; } } return false; } /// /// Returns the factor associated with abbreviations for thousands, millions, and billions. /// If no abbreviations are found we return 1. /// /// The string that may /// Factor associated with using abbreviations. private static double FactorTextBoxText(string text) { text = text.ToUpperInvariant(); double factor = 1.0; if (text.Contains("K")) factor = 1000.0; else if (text.Contains("M")) factor = 1000000.0; else if (text.Contains("B")) factor = 1000000000.0; return factor; } } }