问题
I need to pack string with a UTC datetime, using the smallest number of bytes/characters. I only need precision to the second. Using .NET 4.0, what would be the most space-efficient way to pack this down? Ticks doesn't seem all that small.
All ideas appreciated. Thanks.
EDIT: Thanks to Joel Coehoorn, the pack/unpack move is the best. Thanks! Here is some proof:
DateTimeOffset nowStamp = DateTimeOffset.UtcNow;
Console.WriteLine( nowStamp.ToString() ); // 9/9/2011 2:17:17 PM +00:00
Console.WriteLine( nowStamp.ToString( "u" ) ); // 2011-09-09 14:17:17Z
Console.WriteLine( nowStamp.Ticks.ToString() ); // 634511746376767889
Console.WriteLine( PackDate( nowStamp ) ); // 7R9qTgAAAAA=
Console.WriteLine( UnpackDate( PackDate( nowStamp ) ) ); // 9/9/2011 2:17:17 PM +00:00
回答1:
Perhaps a variant on unix time (seconds since 1/1/1970 rather than milliseconds) base64 encoded.
//Helpers
private static DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public static long toUnixTime(this DateTime d)
{
return (long)((d.ToUniversalTime() - Jan1st1970).TotalMilliseconds);
}
public static string Base64Encode(long toEncode)
{
return Convert.ToBase64String(BitConverter.GetBytes(toEncode));
}
//Encode
public static string PackDate(DateTime toPack)
{
return Base64Encode(toPack.toUnixTime()/1000);
}
//Decode
public static DateTime UnpackDate(string toUnpack)
{
long time = BitConverter.ToInt64(Convert.FromBase64String(toUnpack),0);
return Jan1st1970.AddSeconds(time); //you may or may not want a "ToLocaltime()" call here.
}
Note that all this was done without the aid of an IDE - there's likely a bug or two above. But it should get you started.
This should result in a fixed-width string. Since we're only doing seconds rather than milliseconds, you may find you always have some extra padding in the result that you don't need. You might even be able to get away with an int, rather than a long, which will cut the string in half. Be careful stripping that padding out, though, as the closer you get to 1970 the smaller the number, but the farther you get the larger and the more likely you are to need it. You need to be certain that your date value will fit within the new, smaller range for doing any trimming. For example, the current date fits comfortably within an int, but even 28 years from now will not. UInt32 will get you a little further into the future, but prevent you from using dates before 1970.
回答2:
If you rellay need to save some bytes, and dead sure about date-time bounds, this solution would work:
internal class Program
{
private static DateTime _lbound = new DateTime(2011, 1, 1).ToUniversalTime();
private static DateTime _ubound = new DateTime(2013, 1, 1).ToUniversalTime();
private static int Pack(DateTime utcTime)
{
var totalSeconds = (_ubound - _lbound).TotalSeconds;
return (int) (utcTime - _lbound).TotalSeconds;
}
private static DateTime Unpack(int packedTime)
{
return _lbound.AddSeconds(packedTime);
}
private static void Check(DateTime time)
{
var unpacked = Unpack(Pack(time));
var areEquals = Math.Abs((time - unpacked).TotalSeconds) < 1.0;
Console.WriteLine("Verify: {0} - {1}", time, areEquals);
}
static void Main(string[] args)
{
Check(_lbound);
Check(_ubound);
Check(DateTime.UtcNow);
}
}
It will fit time representation, with 1 second precision in defined time bounds (from 2011 till 2013) in 4 bytes (int). However, IMO it's really bad from maintenance perspective of view.
来源:https://stackoverflow.com/questions/7362174/tightest-byte-representation-of-yyyymmddhhmmss