//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // [....] //------------------------------------------------------------------------------ using System; using System.Collections; using System.IO; using System.Text; using System.Diagnostics; using System.Globalization; namespace System.Xml { // This is mostly just a copy of code in SqlTypes.SqlDecimal internal struct BinXmlSqlDecimal { internal byte m_bLen; internal byte m_bPrec; internal byte m_bScale; internal byte m_bSign; internal uint m_data1; internal uint m_data2; internal uint m_data3; internal uint m_data4; public bool IsPositive { get { return (m_bSign == 0); } } private static readonly byte NUMERIC_MAX_PRECISION = 38; // Maximum precision of numeric private static readonly byte MaxPrecision = NUMERIC_MAX_PRECISION; // max SS precision private static readonly byte MaxScale = NUMERIC_MAX_PRECISION; // max SS scale private static readonly int x_cNumeMax = 4; private static readonly long x_lInt32Base = ((long)1) << 32; // 2**32 private static readonly ulong x_ulInt32Base = ((ulong)1) << 32; // 2**32 private static readonly ulong x_ulInt32BaseForMod = x_ulInt32Base - 1; // 2**32 - 1 (0xFFF...FF) internal static readonly ulong x_llMax = Int64.MaxValue; // Max of Int64 //private static readonly uint x_ulBase10 = 10; private static readonly double DUINT_BASE = (double)x_lInt32Base; // 2**32 private static readonly double DUINT_BASE2 = DUINT_BASE * DUINT_BASE; // 2**64 private static readonly double DUINT_BASE3 = DUINT_BASE2 * DUINT_BASE; // 2**96 //private static readonly double DMAX_NUME = 1.0e+38; // Max value of numeric //private static readonly uint DBL_DIG = 17; // Max decimal digits of double //private static readonly byte x_cNumeDivScaleMin = 6; // Minimum result scale of numeric division // Array of multipliers for lAdjust and Ceiling/Floor. private static readonly uint[] x_rgulShiftBase = new uint[9] { 10, 10 * 10, 10 * 10 * 10, 10 * 10 * 10 * 10, 10 * 10 * 10 * 10 * 10, 10 * 10 * 10 * 10 * 10 * 10, 10 * 10 * 10 * 10 * 10 * 10 * 10, 10 * 10 * 10 * 10 * 10 * 10 * 10 * 10, 10 * 10 * 10 * 10 * 10 * 10 * 10 * 10 * 10 }; public BinXmlSqlDecimal (byte[] data, int offset, bool trim) { byte b = data[offset]; switch (b) { case 7: m_bLen = 1; break; case 11: m_bLen = 2; break; case 15: m_bLen = 3; break; case 19: m_bLen = 4; break; default: throw new XmlException(Res.XmlBinary_InvalidSqlDecimal, (string[])null); } m_bPrec = data[offset+1]; m_bScale = data[offset+2]; m_bSign = 0 == data[offset+3] ? (byte)1 : (byte)0; m_data1 = UIntFromByteArray(data, offset+4); m_data2 = (m_bLen > 1) ? UIntFromByteArray(data, offset+8) : 0; m_data3 = (m_bLen > 2) ? UIntFromByteArray(data, offset+12) : 0; m_data4 = (m_bLen > 3) ? UIntFromByteArray(data, offset+16) : 0; if (m_bLen == 4 && m_data4 == 0) m_bLen = 3; if (m_bLen == 3 && m_data3 == 0) m_bLen = 2; if (m_bLen == 2 && m_data2 == 0) m_bLen = 1; AssertValid(); if (trim) { TrimTrailingZeros(); AssertValid(); } } public void Write(Stream strm) { strm.WriteByte((byte)(this.m_bLen * 4 + 3)); strm.WriteByte(this.m_bPrec); strm.WriteByte(this.m_bScale); strm.WriteByte(0 == this.m_bSign ? (byte)1 : (byte)0); WriteUI4(this.m_data1, strm); if (this.m_bLen > 1) { WriteUI4(this.m_data2, strm); if (this.m_bLen > 2) { WriteUI4(this.m_data3, strm); if (this.m_bLen > 3) { WriteUI4(this.m_data4, strm); } } } } private void WriteUI4(uint val, Stream strm) { strm.WriteByte((byte)(val & 0xFF)); strm.WriteByte((byte)((val >> 8) & 0xFF)); strm.WriteByte((byte)((val >> 16) & 0xFF)); strm.WriteByte((byte)((val >> 24) & 0xFF)); } private static uint UIntFromByteArray(byte[] data, int offset) { int val = (data[offset]) << 0; val |= (data[offset+1]) << 8; val |= (data[offset+2]) << 16; val |= (data[offset+3]) << 24; return unchecked((uint)val); } // check whether is zero private bool FZero() { return (m_data1 == 0) && (m_bLen <= 1); } // Store data back from rguiData[] to m_data* private void StoreFromWorkingArray(uint[] rguiData) { Debug.Assert(rguiData.Length == 4); m_data1 = rguiData[0]; m_data2 = rguiData[1]; m_data3 = rguiData[2]; m_data4 = rguiData[3]; } // Find the case where we overflowed 10**38, but not 2**128 private bool FGt10_38(uint[] rglData) { //Debug.Assert(rglData.Length == 4, "rglData.Length == 4", "Wrong array length: " + rglData.Length.ToString(CultureInfo.InvariantCulture)); return rglData[3] >= 0x4b3b4ca8L && ((rglData[3] > 0x4b3b4ca8L) || (rglData[2] > 0x5a86c47aL) || (rglData[2] == 0x5a86c47aL) && (rglData[1] >= 0x098a2240L)); } // Multi-precision one super-digit divide in place. // U = U / D, // R = U % D // Length of U can decrease private static void MpDiv1(uint[] rgulU, // InOut| U ref int ciulU, // InOut| # of digits in U uint iulD, // In | D out uint iulR // Out | R ) { Debug.Assert(rgulU.Length == x_cNumeMax); uint ulCarry = 0; ulong dwlAccum; ulong ulD = (ulong)iulD; int idU = ciulU; Debug.Assert(iulD != 0, "iulD != 0", "Divided by zero!"); Debug.Assert(iulD > 0, "iulD > 0", "Invalid data: less than zero"); Debug.Assert(ciulU > 0, "ciulU > 0", "No data in the array"); while (idU > 0) { idU--; dwlAccum = (((ulong)ulCarry) << 32) + (ulong)(rgulU[idU]); rgulU[idU] = (uint)(dwlAccum / ulD); ulCarry = (uint)(dwlAccum - (ulong)rgulU[idU] * ulD); // (ULONG) (dwlAccum % iulD) } iulR = ulCarry; MpNormalize(rgulU, ref ciulU); } // Normalize multi-precision number - remove leading zeroes private static void MpNormalize(uint[] rgulU, // In | Number ref int ciulU // InOut| # of digits ) { while (ciulU > 1 && rgulU[ciulU - 1] == 0) ciulU--; } // AdjustScale() // // Adjust number of digits to the right of the decimal point. // A positive adjustment increases the scale of the numeric value // while a negative adjustment decreases the scale. When decreasing // the scale for the numeric value, the remainder is checked and // rounded accordingly. // internal void AdjustScale(int digits, bool fRound) { uint ulRem; //Remainder when downshifting uint ulShiftBase; //What to multiply by to effect scale adjust bool fNeedRound = false; //Do we really need to round? byte bNewScale, bNewPrec; int lAdjust = digits; //If downshifting causes truncation of data if (lAdjust + m_bScale < 0) throw new XmlException(Res.SqlTypes_ArithTruncation, (string)null); //If uphifting causes scale overflow if (lAdjust + m_bScale > NUMERIC_MAX_PRECISION) throw new XmlException(Res.SqlTypes_ArithOverflow, (string)null); bNewScale = (byte)(lAdjust + m_bScale); bNewPrec = (byte)(Math.Min(NUMERIC_MAX_PRECISION, Math.Max(1, lAdjust + m_bPrec))); if (lAdjust > 0) { m_bScale = bNewScale; m_bPrec = bNewPrec; while (lAdjust > 0) { //if lAdjust>=9, downshift by 10^9 each time, otherwise by the full amount if (lAdjust >= 9) { ulShiftBase = x_rgulShiftBase[8]; lAdjust -= 9; } else { ulShiftBase = x_rgulShiftBase[lAdjust - 1]; lAdjust = 0; } MultByULong(ulShiftBase); } } else if (lAdjust < 0) { do { if (lAdjust <= -9) { ulShiftBase = x_rgulShiftBase[8]; lAdjust += 9; } else { ulShiftBase = x_rgulShiftBase[-lAdjust - 1]; lAdjust = 0; } ulRem = DivByULong(ulShiftBase); } while (lAdjust < 0); // Do we really need to round? fNeedRound = (ulRem >= ulShiftBase / 2); m_bScale = bNewScale; m_bPrec = bNewPrec; } AssertValid(); // After adjusting, if the result is 0 and remainder is less than 5, // set the sign to be positive and return. if (fNeedRound && fRound) { // If remainder is 5 or above, increment/decrement by 1. AddULong(1); } else if (FZero()) this.m_bSign = 0; } // AddULong() // // Add ulAdd to this numeric. The result will be returned in *this. // // Parameters: // this - IN Operand1 & OUT Result // ulAdd - IN operand2. // private void AddULong(uint ulAdd) { ulong dwlAccum = (ulong)ulAdd; int iData; // which UI4 in this we are on int iDataMax = (int)m_bLen; // # of UI4s in this uint[] rguiData = new uint[4] { m_data1, m_data2, m_data3, m_data4 }; // Add, starting at the LS UI4 until out of UI4s or no carry iData = 0; do { dwlAccum += (ulong)rguiData[iData]; rguiData[iData] = (uint)dwlAccum; // equivalent to mod x_dwlBaseUI4 dwlAccum >>= 32; // equivalent to dwlAccum /= x_dwlBaseUI4; if (0 == dwlAccum) { StoreFromWorkingArray(rguiData); return; } iData++; } while (iData < iDataMax); // There is carry at the end Debug.Assert(dwlAccum < x_ulInt32Base, "dwlAccum < x_lInt32Base", ""); // Either overflowed if (iData == x_cNumeMax) throw new XmlException(Res.SqlTypes_ArithOverflow, (string)null); // Or need to extend length by 1 UI4 rguiData[iData] = (uint)dwlAccum; m_bLen++; if (FGt10_38(rguiData)) throw new XmlException(Res.SqlTypes_ArithOverflow, (string)null); StoreFromWorkingArray(rguiData); } // multiply by a long integer private void MultByULong(uint uiMultiplier) { int iDataMax = m_bLen; // How many UI4s currently in *this ulong dwlAccum = 0; // accumulated sum ulong dwlNextAccum = 0; // accumulation past dwlAccum int iData; // which UI4 in *This we are on. uint[] rguiData = new uint[4] { m_data1, m_data2, m_data3, m_data4 }; for (iData = 0; iData < iDataMax; iData++) { Debug.Assert(dwlAccum < x_ulInt32Base); ulong ulTemp = (ulong)rguiData[iData]; dwlNextAccum = ulTemp * (ulong)uiMultiplier; dwlAccum += dwlNextAccum; if (dwlAccum < dwlNextAccum) // Overflow of int64 add dwlNextAccum = x_ulInt32Base; // how much to add to dwlAccum after div x_dwlBaseUI4 else dwlNextAccum = 0; rguiData[iData] = (uint)dwlAccum; // equivalent to mod x_dwlBaseUI4 dwlAccum = (dwlAccum >> 32) + dwlNextAccum; // equivalent to div x_dwlBaseUI4 } // If any carry, if (dwlAccum != 0) { // Either overflowed Debug.Assert(dwlAccum < x_ulInt32Base, "dwlAccum < x_dwlBaseUI4", "Integer overflow"); if (iDataMax == x_cNumeMax) throw new XmlException(Res.SqlTypes_ArithOverflow, (string)null); // Or extend length by one uint rguiData[iDataMax] = (uint)dwlAccum; m_bLen++; } if (FGt10_38(rguiData)) throw new XmlException(Res.SqlTypes_ArithOverflow, (string)null); StoreFromWorkingArray(rguiData); } // DivByULong() // // Divide numeric value by a ULONG. The result will be returned // in the dividend *this. // // Parameters: // this - IN Dividend & OUT Result // ulDivisor - IN Divisor // Returns: - OUT Remainder // internal uint DivByULong(uint iDivisor) { ulong dwlDivisor = (ulong)iDivisor; ulong dwlAccum = 0; //Accumulated sum uint ulQuotientCur = 0; // Value of the current UI4 of the quotient bool fAllZero = true; // All of the quotient (so far) has been 0 int iData; //Which UI4 currently on // Check for zero divisor. if (dwlDivisor == 0) throw new XmlException(Res.SqlTypes_DivideByZero, (string)null); // Copy into array, so that we can iterate through the data uint[] rguiData = new uint[4] { m_data1, m_data2, m_data3, m_data4 }; // Start from the MS UI4 of quotient, divide by divisor, placing result // in quotient and carrying the remainder. //DEVNOTE DWORDLONG sufficient accumulator since: // Accum < Divisor <= 2^32 - 1 at start each loop // initially,and mod end previous loop // Accum*2^32 < 2^64 - 2^32 // multiply both side by 2^32 (x_dwlBaseUI4) // Accum*2^32 + m_rgulData < 2^64 // rglData < 2^32 for (iData = m_bLen; iData > 0; iData--) { Debug.Assert(dwlAccum < dwlDivisor); dwlAccum = (dwlAccum << 32) + (ulong)(rguiData[iData - 1]); // dwlA*x_dwlBaseUI4 + rglData Debug.Assert((dwlAccum / dwlDivisor) < x_ulInt32Base); //Update dividend to the quotient. ulQuotientCur = (uint)(dwlAccum / dwlDivisor); rguiData[iData - 1] = ulQuotientCur; //Remainder to be carried to the next lower significant byte. dwlAccum = dwlAccum % dwlDivisor; // While current part of quotient still 0, reduce length fAllZero = fAllZero && (ulQuotientCur == 0); if (fAllZero) m_bLen--; } StoreFromWorkingArray(rguiData); // If result is 0, preserve sign but set length to 5 if (fAllZero) m_bLen = 1; AssertValid(); // return the remainder Debug.Assert(dwlAccum < x_ulInt32Base); return (uint)dwlAccum; } //Determine the number of uints needed for a numeric given a precision //Precision Length // 0 invalid // 1-9 1 // 10-19 2 // 20-28 3 // 29-38 4 // The array in Shiloh. Listed here for comparison. //private static readonly byte[] rgCLenFromPrec = new byte[] {5,5,5,5,5,5,5,5,5,9,9,9,9,9, // 9,9,9,9,9,13,13,13,13,13,13,13,13,13,17,17,17,17,17,17,17,17,17,17}; private static readonly byte[] rgCLenFromPrec = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 }; private static byte CLenFromPrec(byte bPrec) { Debug.Assert(bPrec <= MaxPrecision && bPrec > 0, "bPrec <= MaxPrecision && bPrec > 0", "Invalid numeric precision"); return rgCLenFromPrec[bPrec - 1]; } private static char ChFromDigit(uint uiDigit) { Debug.Assert(uiDigit < 10); return (char)(uiDigit + '0'); } public Decimal ToDecimal() { if ((int)m_data4 != 0 || m_bScale > 28) throw new XmlException(Res.SqlTypes_ArithOverflow, (string)null); return new Decimal((int)m_data1, (int)m_data2, (int)m_data3, !IsPositive, m_bScale); } void TrimTrailingZeros() { uint[] rgulNumeric = new uint[4] { m_data1, m_data2, m_data3, m_data4}; int culLen = m_bLen; uint ulRem; //Remainder of a division by x_ulBase10, i.e.,least significant digit // special-case 0 if (culLen == 1 && rgulNumeric[0] == 0) { m_bScale = 0; return; } while (m_bScale > 0 && (culLen > 1 || rgulNumeric[0] != 0)) { MpDiv1 (rgulNumeric, ref culLen, 10, out ulRem); if ( ulRem == 0 ) { m_data1 = rgulNumeric[0]; m_data2 = rgulNumeric[1]; m_data3 = rgulNumeric[2]; m_data4 = rgulNumeric[3]; m_bScale--; } else { break; } } if (m_bLen == 4 && m_data4 == 0) m_bLen = 3; if (m_bLen == 3 && m_data3 == 0) m_bLen = 2; if (m_bLen == 2 && m_data2 == 0) m_bLen = 1; } public override String ToString() { AssertValid(); // Make local copy of data to avoid modifying input. uint[] rgulNumeric = new uint[4] { m_data1, m_data2, m_data3, m_data4}; int culLen = m_bLen; char[] pszTmp = new char[NUMERIC_MAX_PRECISION + 1]; //Local Character buffer to hold //the decimal digits, from the //lowest significant to highest significant int iDigits = 0;//Number of significant digits uint ulRem; //Remainder of a division by x_ulBase10, i.e.,least significant digit // Build the final numeric string by inserting the sign, reversing // the order and inserting the decimal number at the correct position //Retrieve each digit from the lowest significant digit while (culLen > 1 || rgulNumeric[0] != 0) { MpDiv1 (rgulNumeric, ref culLen, 10, out ulRem); //modulo x_ulBase10 is the lowest significant digit pszTmp[iDigits++] = ChFromDigit(ulRem); } // if scale of the number has not been // reached pad remaining number with zeros. while (iDigits <= m_bScale) { pszTmp[iDigits++] = ChFromDigit(0); } bool fPositive = IsPositive; // Increment the result length if negative (need to add '-') int uiResultLen = fPositive ? iDigits : iDigits + 1; // Increment the result length if scale > 0 (need to add '.') if (m_bScale > 0) uiResultLen++; char[] szResult = new char[uiResultLen]; int iCurChar = 0; if (!fPositive) szResult[iCurChar ++] = '-'; while (iDigits > 0) { if (iDigits-- == m_bScale) szResult[iCurChar ++] = '.'; szResult[iCurChar ++] = pszTmp[iDigits]; } AssertValid(); return new String(szResult); } // Is this RE numeric valid? [System.Diagnostics.Conditional("DEBUG")] private void AssertValid() { // Scale,Prec in range Debug.Assert(m_bScale <= NUMERIC_MAX_PRECISION, "m_bScale <= NUMERIC_MAX_PRECISION", "In AssertValid"); Debug.Assert(m_bScale <= m_bPrec, "m_bScale <= m_bPrec", "In AssertValid"); Debug.Assert(m_bScale >= 0, "m_bScale >= 0", "In AssertValid"); Debug.Assert(m_bPrec > 0, "m_bPrec > 0", "In AssertValid"); Debug.Assert(CLenFromPrec(m_bPrec) >= m_bLen, "CLenFromPrec(m_bPrec) >= m_bLen", "In AssertValid"); Debug.Assert(m_bLen <= x_cNumeMax, "m_bLen <= x_cNumeMax", "In AssertValid"); uint[] rglData = new uint[4] { m_data1, m_data2, m_data3, m_data4 }; // highest UI4 is non-0 unless value "zero" if (rglData[m_bLen - 1] == 0) { Debug.Assert(m_bLen == 1, "m_bLen == 1", "In AssertValid"); } // All UI4s from length to end are 0 for (int iulData = m_bLen; iulData < x_cNumeMax; iulData++) Debug.Assert(rglData[iulData] == 0, "rglData[iulData] == 0", "In AssertValid"); } } internal struct BinXmlSqlMoney { long data; public BinXmlSqlMoney(int v) { this.data = v; } public BinXmlSqlMoney(long v) { this.data = v; } public Decimal ToDecimal() { bool neg; ulong v; if (this.data < 0) { neg = true; v = (ulong)unchecked(-this.data); } else { neg = false; v = (ulong)this.data; } // SQL Server stores money8 as ticks of 1/10000. const byte MoneyScale = 4; return new Decimal(unchecked((int)v), unchecked((int)(v >> 32)), 0, neg, MoneyScale); } public override String ToString() { Decimal money = ToDecimal(); // Formatting of SqlMoney: At least two digits after decimal point return money.ToString("#0.00##", CultureInfo.InvariantCulture); } } internal abstract class BinXmlDateTime { const int MaxFractionDigits = 7; static internal int[] KatmaiTimeScaleMultiplicator = new int[8] { 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1, }; static void Write2Dig( StringBuilder sb, int val ) { Debug.Assert(val >= 0 && val < 100); sb.Append((char)('0' + (val/10))); sb.Append((char)('0' + (val%10))); } static void Write4DigNeg(StringBuilder sb, int val) { Debug.Assert(val > -10000 && val < 10000); if (val < 0) { val = -val; sb.Append('-'); } Write2Dig(sb, val/100); Write2Dig(sb, val%100); } static void Write3Dec(StringBuilder sb, int val) { Debug.Assert(val >= 0 && val < 1000); int c3 = val % 10; val /= 10; int c2 = val % 10; val /= 10; int c1 = val; sb.Append('.'); sb.Append((char)('0'+c1)); sb.Append((char)('0'+c2)); sb.Append((char)('0'+c3)); } static void WriteDate(StringBuilder sb, int yr, int mnth, int day) { Write4DigNeg(sb, yr); sb.Append('-'); Write2Dig(sb, mnth); sb.Append('-'); Write2Dig(sb, day); } static void WriteTime(StringBuilder sb, int hr, int min, int sec, int ms) { Write2Dig(sb, hr); sb.Append(':'); Write2Dig(sb, min); sb.Append(':'); Write2Dig(sb, sec); if (ms != 0) { Write3Dec(sb, ms); } } static void WriteTimeFullPrecision(StringBuilder sb, int hr, int min, int sec, int fraction) { Write2Dig(sb, hr); sb.Append(':'); Write2Dig(sb, min); sb.Append(':'); Write2Dig(sb, sec); if (fraction != 0) { int fractionDigits = MaxFractionDigits; while (fraction % 10 == 0) { fractionDigits --; fraction /= 10; } char[] charArray = new char[fractionDigits]; while(fractionDigits > 0) { fractionDigits--; charArray[fractionDigits] = (char)(fraction % 10 + '0'); fraction /= 10; } sb.Append('.'); sb.Append(charArray); } } static void WriteTimeZone(StringBuilder sb, TimeSpan zone) { bool negTimeZone = true; if (zone.Ticks < 0) { negTimeZone = false; zone = zone.Negate(); } WriteTimeZone(sb, negTimeZone, zone.Hours, zone.Minutes); } static void WriteTimeZone(StringBuilder sb, bool negTimeZone, int hr, int min) { if (hr == 0 && min == 0) { sb.Append('Z'); } else { sb.Append(negTimeZone ? '+' : '-'); Write2Dig(sb, hr); sb.Append(':'); Write2Dig(sb, min); } } static void BreakDownXsdDateTime(long val, out int yr, out int mnth, out int day, out int hr, out int min, out int sec, out int ms) { if (val < 0) goto Error; long date = val / 4; // trim indicator bits ms = (int)(date % 1000); date /= 1000; sec = (int)(date % 60); date /= 60; min = (int)(date % 60); date /= 60; hr = (int)(date % 24); date /= 24; day = (int)(date % 31) + 1; date /= 31; mnth = (int)(date % 12) + 1; date /= 12; yr = (int)(date - 9999); if (yr < -9999 || yr > 9999) goto Error; return; Error: throw new XmlException(Res.SqlTypes_ArithOverflow, (string)null); } static void BreakDownXsdDate(long val, out int yr, out int mnth, out int day, out bool negTimeZone, out int hr, out int min) { if (val < 0) goto Error; val = val / 4; // trim indicator bits int totalMin = (int)(val % (29*60)) - 60*14; long totalDays = val / (29*60); if (negTimeZone = (totalMin < 0)) totalMin = -totalMin; min = totalMin % 60; hr = totalMin / 60; day = (int)(totalDays % 31) + 1; totalDays /= 31; mnth = (int)(totalDays % 12) + 1; yr = (int)(totalDays / 12) - 9999; if (yr < -9999 || yr > 9999) goto Error; return; Error: throw new XmlException(Res.SqlTypes_ArithOverflow, (string)null); } static void BreakDownXsdTime(long val, out int hr, out int min, out int sec, out int ms) { if (val < 0) goto Error; val = val / 4; // trim indicator bits ms = (int)(val % 1000); val /= 1000; sec = (int)(val % 60); val /= 60; min = (int)(val % 60); hr = (int)(val / 60); if (0 > hr || hr > 23) goto Error; return; Error: throw new XmlException(Res.SqlTypes_ArithOverflow, (string)null); } public static string XsdDateTimeToString(long val) { int yr; int mnth; int day; int hr; int min; int sec; int ms; BreakDownXsdDateTime(val, out yr, out mnth, out day, out hr, out min, out sec, out ms); StringBuilder sb = new StringBuilder(20); WriteDate(sb, yr, mnth, day); sb.Append('T'); WriteTime(sb, hr, min, sec, ms); sb.Append('Z'); return sb.ToString(); } public static DateTime XsdDateTimeToDateTime(long val) { int yr; int mnth; int day; int hr; int min; int sec; int ms; BreakDownXsdDateTime(val, out yr, out mnth, out day, out hr, out min, out sec, out ms); return new DateTime(yr, mnth, day, hr, min, sec, ms, DateTimeKind.Utc); } public static string XsdDateToString(long val) { int yr; int mnth; int day; int hr; int min; bool negTimeZ; BreakDownXsdDate(val, out yr, out mnth, out day, out negTimeZ, out hr, out min); StringBuilder sb = new StringBuilder(20); WriteDate(sb, yr, mnth, day); WriteTimeZone(sb, negTimeZ, hr, min); return sb.ToString(); } public static DateTime XsdDateToDateTime(long val) { int yr; int mnth; int day; int hr; int min; bool negTimeZ; BreakDownXsdDate(val, out yr, out mnth, out day, out negTimeZ, out hr, out min); DateTime d = new DateTime(yr, mnth, day, 0, 0, 0, DateTimeKind.Utc); // adjust for timezone int adj = (negTimeZ ? -1 : 1) * ( (hr * 60) + min ); return TimeZone.CurrentTimeZone.ToLocalTime( d.AddMinutes(adj) ); } public static string XsdTimeToString(long val) { int hr; int min; int sec; int ms; BreakDownXsdTime(val, out hr, out min, out sec, out ms); StringBuilder sb = new StringBuilder(16); WriteTime(sb, hr, min, sec, ms); sb.Append('Z'); return sb.ToString(); } public static DateTime XsdTimeToDateTime(long val) { int hr; int min; int sec; int ms; BreakDownXsdTime(val, out hr, out min, out sec, out ms); return new DateTime(1, 1, 1, hr, min, sec, ms, DateTimeKind.Utc); } public static string SqlDateTimeToString(int dateticks, uint timeticks) { DateTime dateTime = SqlDateTimeToDateTime(dateticks, timeticks); string format = (dateTime.Millisecond != 0) ? "yyyy/MM/dd\\THH:mm:ss.ffff" : "yyyy/MM/dd\\THH:mm:ss"; return dateTime.ToString(format, CultureInfo.InvariantCulture); } public static DateTime SqlDateTimeToDateTime(int dateticks, uint timeticks) { DateTime SQLBaseDate = new DateTime(1900, 1, 1); //long millisecond = (long)(((ulong)timeticks * 20 + (ulong)3) / (ulong)6); long millisecond = (long)(timeticks / SQLTicksPerMillisecond + 0.5); return SQLBaseDate.Add( new TimeSpan( dateticks * TimeSpan.TicksPerDay + millisecond * TimeSpan.TicksPerMillisecond ) ); } // Number of (100ns) ticks per time unit private static readonly double SQLTicksPerMillisecond = 0.3; public static readonly int SQLTicksPerSecond = 300; public static readonly int SQLTicksPerMinute = SQLTicksPerSecond * 60; public static readonly int SQLTicksPerHour = SQLTicksPerMinute * 60; private static readonly int SQLTicksPerDay = SQLTicksPerHour * 24; public static string SqlSmallDateTimeToString(short dateticks, ushort timeticks) { DateTime dateTime = SqlSmallDateTimeToDateTime(dateticks, timeticks); return dateTime.ToString("yyyy/MM/dd\\THH:mm:ss", CultureInfo.InvariantCulture); } public static DateTime SqlSmallDateTimeToDateTime(short dateticks, ushort timeticks) { return SqlDateTimeToDateTime( (int)dateticks, (uint)(timeticks * SQLTicksPerMinute) ); } // Conversions of the Katmai date & time types to DateTime public static DateTime XsdKatmaiDateToDateTime(byte[] data, int offset) { // Katmai SQL type "DATE" long dateTicks = GetKatmaiDateTicks(data, ref offset); DateTime dt = new DateTime(dateTicks); return dt; } public static DateTime XsdKatmaiDateTimeToDateTime(byte[] data, int offset) { // Katmai SQL type "DATETIME2" long timeTicks = GetKatmaiTimeTicks(data, ref offset); long dateTicks = GetKatmaiDateTicks(data, ref offset); DateTime dt = new DateTime(dateTicks + timeTicks); return dt; } public static DateTime XsdKatmaiTimeToDateTime(byte[] data, int offset) { // TIME without zone is stored as DATETIME2 return XsdKatmaiDateTimeToDateTime(data, offset); } public static DateTime XsdKatmaiDateOffsetToDateTime( byte[] data, int offset ) { // read the timezoned value into DateTimeOffset and then convert to local time return XsdKatmaiDateOffsetToDateTimeOffset(data, offset).LocalDateTime; } public static DateTime XsdKatmaiDateTimeOffsetToDateTime(byte[] data, int offset) { // read the timezoned value into DateTimeOffset and then convert to local time return XsdKatmaiDateTimeOffsetToDateTimeOffset(data, offset).LocalDateTime; } public static DateTime XsdKatmaiTimeOffsetToDateTime(byte[] data, int offset) { // read the timezoned value into DateTimeOffset and then convert to local time return XsdKatmaiTimeOffsetToDateTimeOffset(data, offset).LocalDateTime; } // Conversions of the Katmai date & time types to DateTimeOffset public static DateTimeOffset XsdKatmaiDateToDateTimeOffset( byte[] data, int offset ) { // read the value into DateTime and then convert it to DateTimeOffset, which adds local time zone return (DateTimeOffset)XsdKatmaiDateToDateTime(data, offset); } public static DateTimeOffset XsdKatmaiDateTimeToDateTimeOffset(byte[] data, int offset) { // read the value into DateTime and then convert it to DateTimeOffset, which adds local time zone return (DateTimeOffset)XsdKatmaiDateTimeToDateTime(data, offset); } public static DateTimeOffset XsdKatmaiTimeToDateTimeOffset(byte[] data, int offset) { // read the value into DateTime and then convert it to DateTimeOffset, which adds local time zone return (DateTimeOffset)XsdKatmaiTimeToDateTime(data, offset); } public static DateTimeOffset XsdKatmaiDateOffsetToDateTimeOffset(byte[] data, int offset) { // DATE with zone is stored as DATETIMEOFFSET return XsdKatmaiDateTimeOffsetToDateTimeOffset(data, offset); } public static DateTimeOffset XsdKatmaiDateTimeOffsetToDateTimeOffset(byte[] data, int offset) { // Katmai SQL type "DATETIMEOFFSET" long timeTicks = GetKatmaiTimeTicks(data, ref offset); long dateTicks = GetKatmaiDateTicks(data, ref offset); long zoneTicks = GetKatmaiTimeZoneTicks(data, offset); // The DATETIMEOFFSET values are serialized in UTC, but DateTimeOffset takes adjusted time -> we need to add zoneTicks DateTimeOffset dto = new DateTimeOffset(dateTicks + timeTicks + zoneTicks, new TimeSpan(zoneTicks)); return dto; } public static DateTimeOffset XsdKatmaiTimeOffsetToDateTimeOffset(byte[] data, int offset) { // TIME with zone is stored as DATETIMEOFFSET return XsdKatmaiDateTimeOffsetToDateTimeOffset(data, offset); } // Conversions of the Katmai date & time types to string public static string XsdKatmaiDateToString(byte[] data, int offset) { DateTime dt = XsdKatmaiDateToDateTime(data, offset); StringBuilder sb = new StringBuilder(10); WriteDate(sb, dt.Year, dt.Month, dt.Day); return sb.ToString(); } public static string XsdKatmaiDateTimeToString(byte[] data, int offset) { DateTime dt = XsdKatmaiDateTimeToDateTime(data, offset); StringBuilder sb = new StringBuilder(33); WriteDate(sb, dt.Year, dt.Month, dt.Day); sb.Append('T'); WriteTimeFullPrecision(sb, dt.Hour, dt.Minute, dt.Second, GetFractions(dt)); return sb.ToString(); } public static string XsdKatmaiTimeToString(byte[] data, int offset) { DateTime dt = XsdKatmaiTimeToDateTime(data, offset); StringBuilder sb = new StringBuilder(16); WriteTimeFullPrecision(sb, dt.Hour, dt.Minute, dt.Second, GetFractions(dt)); return sb.ToString(); } public static string XsdKatmaiDateOffsetToString(byte[] data, int offset) { DateTimeOffset dto = XsdKatmaiDateOffsetToDateTimeOffset(data, offset); StringBuilder sb = new StringBuilder(16); WriteDate(sb, dto.Year, dto.Month, dto.Day); WriteTimeZone(sb, dto.Offset); return sb.ToString(); } public static string XsdKatmaiDateTimeOffsetToString(byte[] data, int offset) { DateTimeOffset dto = XsdKatmaiDateTimeOffsetToDateTimeOffset(data, offset); StringBuilder sb = new StringBuilder(39); WriteDate(sb, dto.Year, dto.Month, dto.Day); sb.Append('T'); WriteTimeFullPrecision(sb, dto.Hour, dto.Minute, dto.Second, GetFractions(dto)); WriteTimeZone(sb, dto.Offset); return sb.ToString(); } public static string XsdKatmaiTimeOffsetToString(byte[] data, int offset) { DateTimeOffset dto = XsdKatmaiTimeOffsetToDateTimeOffset(data, offset); StringBuilder sb = new StringBuilder(22); WriteTimeFullPrecision(sb, dto.Hour, dto.Minute, dto.Second, GetFractions(dto)); WriteTimeZone(sb, dto.Offset); return sb.ToString(); } // Helper methods for the Katmai date & time types static long GetKatmaiDateTicks(byte[] data, ref int pos) { int p = pos; pos = p + 3; return (data[p] | data[p + 1] << 8 | data[p + 2] << 16) * TimeSpan.TicksPerDay; } static long GetKatmaiTimeTicks(byte[] data, ref int pos) { int p = pos; byte scale = data[p]; long timeTicks; p++; if (scale <= 2) { timeTicks = data[p] | (data[p + 1] << 8) | (data[p + 2] << 16); pos = p + 3; } else if (scale <= 4) { timeTicks = data[p] | (data[p + 1] << 8) | (data[p + 2] << 16); timeTicks |= ((long)data[p + 3] << 24); pos = p + 4; } else if (scale <= 7) { timeTicks = data[p] | (data[p + 1] << 8) | (data[p + 2] << 16); timeTicks |= ((long)data[p + 3] << 24) | ((long)data[p + 4] << 32); pos = p + 5; } else { throw new XmlException(Res.SqlTypes_ArithOverflow, (string)null); } return timeTicks * KatmaiTimeScaleMultiplicator[scale]; } static long GetKatmaiTimeZoneTicks(byte[] data, int pos) { return (short)(data[pos] | data[pos + 1] << 8) * TimeSpan.TicksPerMinute; } static int GetFractions(DateTime dt) { return (int)(dt.Ticks - new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second).Ticks); } static int GetFractions(DateTimeOffset dt) { return (int)(dt.Ticks - new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second).Ticks); } /* const long SqlDateTicks2Ticks = (long)10000 * 1000 * 60 * 60 * 24; const long SqlBaseDate = 693595; public static void DateTime2SqlDateTime(DateTime datetime, out int dateticks, out uint timeticks) { dateticks = (int)(datetime.Ticks / SqlDateTicks2Ticks) - 693595; double time = (double)(datetime.Ticks % SqlDateTicks2Ticks); time = time / 10000; // adjust to ms time = time * 0.3 + .5; // adjust to sqlticks (and round correctly) timeticks = (uint)time; } public static void DateTime2SqlSmallDateTime(DateTime datetime, out short dateticks, out ushort timeticks) { dateticks = (short)((int)(datetime.Ticks / SqlDateTicks2Ticks) - 693595); int time = (int)(datetime.Ticks % SqlDateTicks2Ticks); timeticks = (ushort)(time / (10000 * 1000 * 60)); // adjust to min } public static long DateTime2XsdTime(DateTime datetime) { // adjust to ms return (datetime.TimeOfDay.Ticks / 10000) * 4 + 0; } public static long DateTime2XsdDateTime(DateTime datetime) { long t = datetime.TimeOfDay.Ticks / 10000; t += (datetime.Day-1) * (long)1000*60*60*24; t += (datetime.Month-1) * (long)1000*60*60*24*31; int year = datetime.Year; if (year < -9999 || year > 9999) throw new XmlException(Res.SqlTypes_ArithOverflow, (string)null); t += (datetime.Year+9999) * (long)1000*60*60*24*31*12; return t*4 + 2; } public static long DateTime2XsdDate(DateTime datetime) { // compute local offset long tzOffset = -TimeZone.CurrentTimeZone.GetUtcOffset(datetime).Ticks / TimeSpan.TicksPerMinute; tzOffset += 14*60; // adjust datetime to UTC datetime = TimeZone.CurrentTimeZone.ToUniversalTime(datetime); Debug.Assert( tzOffset >= 0 ); int year = datetime.Year; if (year < -9999 || year > 9999) throw new XmlException(Res.SqlTypes_ArithOverflow, (string)null); long t = (datetime.Day - 1) + 31*(datetime.Month - 1) + 31*12*((long)(year+9999)); t *= (29*60); // adjust in timezone t += tzOffset; return t*4+1; } * */ } }