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.
///
public static 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.
///
public static 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.
///
///
public static 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 hundredth? When talking to the server we always want
/// a period to mean the decimal point. We allow commas as number separators,
/// 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 separator.
/// 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 separator.
/// 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);
}
///
/// Use this to parse decimal numbers from the server.
///
/// Decimal 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 hundredth? When talking to the server we always want
/// a period to mean the decimal point. We allow commas as number separators,
/// 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 decimal result)
{
return Decimal.TryParse(source, NumberStyles.Float | 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 abbreviations to a double.
/// Abbreviations 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 TryParseWithAbbreviations(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;
}
///
/// Convert double to string in US format.
/// Result will have periods as decimal separators.
///
///
///
public static string ToJavascriptString(this double d, string format = "")
{
if (!string.IsNullOrEmpty(format))
return d.ToString(format, CultureInfo.InvariantCulture);
else
return d.ToString(CultureInfo.InvariantCulture);
}
///
/// Convert DateTime to Javascript milliseconds.
///
///
///
public static long ToJavascriptMs(this DateTime dateTime)
{
DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
TimeSpan timeSpan = dateTime.Subtract(unixEpoch);
return (long)timeSpan.TotalMilliseconds;
}
}
}