/* Copyright 2010-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
namespace MongoDB.Bson
{
///
/// A static class containing BSON utility methods.
///
public static class BsonUtils
{
// public static methods
///
/// Gets a friendly class name suitable for use in error messages.
///
/// The type.
/// A friendly class name.
public static string GetFriendlyTypeName(Type type)
{
var typeInfo = type.GetTypeInfo();
if (!typeInfo.IsGenericType)
{
return type.Name;
}
var sb = new StringBuilder();
sb.AppendFormat("{0}<", Regex.Replace(type.Name, @"\`\d+$", ""));
foreach (var typeParameter in typeInfo.GetGenericArguments())
{
sb.AppendFormat("{0}, ", GetFriendlyTypeName(typeParameter));
}
sb.Remove(sb.Length - 2, 2);
sb.Append(">");
return sb.ToString();
}
///
/// Parses a hex string into its equivalent byte array.
///
/// The hex string to parse.
/// The byte equivalent of the hex string.
public static byte[] ParseHexString(string s)
{
if (s == null)
{
throw new ArgumentNullException(nameof(s));
}
byte[] bytes;
if (!TryParseHexString(s, out bytes))
{
throw new FormatException("String should contain only hexadecimal digits.");
}
return bytes;
}
///
/// Converts from number of milliseconds since Unix epoch to DateTime.
///
/// The number of milliseconds since Unix epoch.
/// A DateTime.
public static DateTime ToDateTimeFromMillisecondsSinceEpoch(long millisecondsSinceEpoch)
{
if (millisecondsSinceEpoch < BsonConstants.DateTimeMinValueMillisecondsSinceEpoch ||
millisecondsSinceEpoch > BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch)
{
var message = string.Format(
"The value {0} for the BsonDateTime MillisecondsSinceEpoch is outside the" +
"range that can be converted to a .NET DateTime.",
millisecondsSinceEpoch);
throw new ArgumentOutOfRangeException("millisecondsSinceEpoch", message);
}
// MaxValue has to be handled specially to avoid rounding errors
if (millisecondsSinceEpoch == BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch)
{
return DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc);
}
else
{
return BsonConstants.UnixEpoch.AddTicks(millisecondsSinceEpoch * 10000);
}
}
///
/// Converts a value to a hex character.
///
/// The value (assumed to be between 0 and 15).
/// The hex character.
public static char ToHexChar(int value)
{
return (char)(value + (value < 10 ? '0' : 'a' - 10));
}
///
/// Converts a byte array to a hex string.
///
/// The byte array.
/// A hex string.
public static string ToHexString(byte[] bytes)
{
if (bytes == null)
{
throw new ArgumentNullException("bytes");
}
var length = bytes.Length;
var c = new char[length * 2];
for (int i = 0, j = 0; i < length; i++)
{
var b = bytes[i];
c[j++] = ToHexChar(b >> 4);
c[j++] = ToHexChar(b & 0x0f);
}
return new string(c);
}
///
/// Converts a DateTime to local time (with special handling for MinValue and MaxValue).
///
/// A DateTime.
/// The DateTime in local time.
public static DateTime ToLocalTime(DateTime dateTime)
{
if (dateTime == DateTime.MinValue)
{
return DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Local);
}
else if (dateTime == DateTime.MaxValue)
{
return DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Local);
}
else
{
return dateTime.ToLocalTime();
}
}
///
/// Converts a DateTime to number of milliseconds since Unix epoch.
///
/// A DateTime.
/// Number of milliseconds since Unix epoch.
public static long ToMillisecondsSinceEpoch(DateTime dateTime)
{
var utcDateTime = ToUniversalTime(dateTime);
return (utcDateTime - BsonConstants.UnixEpoch).Ticks / 10000;
}
///
/// Converts a DateTime to number of seconds since Unix epoch.
///
/// A DateTime.
/// Number of seconds since Unix epoch.
public static long ToSecondsSinceEpoch(DateTime dateTime)
{
var utcDateTime = ToUniversalTime(dateTime);
return (utcDateTime - BsonConstants.UnixEpoch).Ticks / TimeSpan.TicksPerSecond;
}
///
/// Converts a DateTime to UTC (with special handling for MinValue and MaxValue).
///
/// A DateTime.
/// The DateTime in UTC.
public static DateTime ToUniversalTime(DateTime dateTime)
{
if (dateTime == DateTime.MinValue)
{
return DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
}
else if (dateTime == DateTime.MaxValue)
{
return DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc);
}
else
{
return dateTime.ToUniversalTime();
}
}
///
/// Tries to parse a hex string to a byte array.
///
/// The hex string.
/// A byte array.
/// True if the hex string was successfully parsed.
public static bool TryParseHexString(string s, out byte[] bytes)
{
bytes = null;
if (s == null)
{
return false;
}
var buffer = new byte[(s.Length + 1) / 2];
var i = 0;
var j = 0;
if ((s.Length % 2) == 1)
{
// if s has an odd length assume an implied leading "0"
int y;
if (!TryParseHexChar(s[i++], out y))
{
return false;
}
buffer[j++] = (byte)y;
}
while (i < s.Length)
{
int x, y;
if (!TryParseHexChar(s[i++], out x))
{
return false;
}
if (!TryParseHexChar(s[i++], out y))
{
return false;
}
buffer[j++] = (byte)((x << 4) | y);
}
bytes = buffer;
return true;
}
// private static methods
private static bool TryParseHexChar(char c, out int value)
{
if (c >= '0' && c <= '9')
{
value = c - '0';
return true;
}
if (c >= 'a' && c <= 'f')
{
value = 10 + (c - 'a');
return true;
}
if (c >= 'A' && c <= 'F')
{
value = 10 + (c - 'A');
return true;
}
value = 0;
return false;
}
}
}