Summary

Class:E_CBAM
Assembly:CBAM.SQL.PostgreSQL
File(s):/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL/PgSQLException.cs
/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL/TypeRegistry.cs
/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL/Types.cs
Covered lines:16
Uncovered lines:28
Coverable lines:44
Total lines:5263
Line coverage:36.3%
Branch coverage:27.9%

Coverage History

Metrics

MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage
HasErrorCodes(...)600%0%
ReadBackendValueCheckNull()2200%0%
GetBackendSizeCheckNull(...)201%1%
WriteBackendValueCheckNull()701%1%
EqualsOrThrow(...)200.75%0.5%
TryParseOptionalNumber(...)400.75%0.5%

File(s)

/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL/PgSQLException.cs

#LineLine coverage
 1/*
 2 * Copyright 2017 Stanislav Muhametsin. All rights Reserved.
 3 *
 4 * Licensed  under the  Apache License,  Version 2.0  (the "License");
 5 * you may not use  this file  except in  compliance with the License.
 6 * You may obtain a copy of the License at
 7 *
 8 *   http://www.apache.org/licenses/LICENSE-2.0
 9 *
 10 * Unless required by applicable law or agreed to in writing, software
 11 * distributed  under the  License is distributed on an "AS IS" BASIS,
 12 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
 13 * implied.
 14 *
 15 * See the License for the specific language governing permissions and
 16 * limitations under the License.
 17 */
 18using CBAM.SQL.PostgreSQL;
 19using System;
 20using System.Collections.Generic;
 21using System.Linq;
 22using System.Text;
 23using UtilPack;
 24
 25namespace CBAM.SQL.PostgreSQL
 26{
 27   /// <summary>
 28   /// This class extends <see cref="SQLException"/> to provide PostgreSQL-specific <see cref="PgSQLError"/> objects des
 29   /// </summary>
 30   /// <seealso cref="PgSQLError"/>
 31   public sealed class PgSQLException : SQLException
 32   {
 33      /// <summary>
 34      /// Creates a new instance of <see cref="PgSQLException"/> with one instance of backend <see cref="PgSQLError"/>.
 35      /// </summary>
 36      /// <param name="error">The information about backend error.</param>
 37      public PgSQLException( PgSQLError error )
 38         : base( error?.ToString() )
 39      {
 40         this.Errors = error == null ? Empty<PgSQLError>.Array : new PgSQLError[] { error };
 41      }
 42
 43      /// <summary>
 44      /// Creates a new instance of <see cref="PgSQLException"/> with many instances of backend <see cref="PgSQLError"/>
 45      /// </summary>
 46      /// <param name="errors">Information about backend errors.</param>
 47      public PgSQLException( IList<PgSQLError> errors )
 48         : base( errors == null || errors.Count == 0 ? null : errors[0].ToString() )
 49      {
 50         this.Errors = errors?.ToArray() ?? Empty<PgSQLError>.Array;
 51      }
 52
 53      /// <summary>
 54      /// Creates a new instance of <see cref="PgSQLException"/> with given message and optional inner exception.
 55      /// </summary>
 56      /// <param name="msg">The textual message describing an error.</param>
 57      /// <param name="cause">The optional inner exception.</param>
 58      public PgSQLException( String msg, Exception cause = null )
 59         : base( msg, cause )
 60      {
 61         this.Errors = Empty<PgSQLError>.Array;
 62      }
 63
 64      /// <summary>
 65      /// Gets the array of all backend errors this <see cref="PgSQLException"/> holds.
 66      /// </summary>
 67      /// <value>The array of all backend errors this <see cref="PgSQLException"/> holds.</value>
 68      /// <seealso cref="PgSQLError"/>
 69      public PgSQLError[] Errors { get; }
 70
 71   }
 72
 73   /// <summary>
 74   /// This class encapsulates all information about an error occurred at PostgreSQL backend process.
 75   /// </summary>
 76   public sealed class PgSQLError
 77   {
 78      /// <summary>
 79      /// Creates a new instance of <see cref="PgSQLError"/> with given parameters.
 80      /// Any and all of the parameters may be <c>null</c>.
 81      /// </summary>
 82      /// <param name="severity">The error severity.</param>
 83      /// <param name="code">The error code.</param>
 84      /// <param name="message">The informative error message.</param>
 85      /// <param name="detail">Information about the details of error.</param>
 86      /// <param name="hint">Any possible hint about detecting or avoiding an error.</param>
 87      /// <param name="position">The position information in SQL code.</param>
 88      /// <param name="internalPosition">Internal position information in source code.</param>
 89      /// <param name="internalQuery">Internal query.</param>
 90      /// <param name="where">Function information in source code where error occurred.</param>
 91      /// <param name="file">The file name of source code where error occurred.</param>
 92      /// <param name="line">The line number of source code file where error occurred.</param>
 93      /// <param name="routine">Routine name, if applicable.</param>
 94      /// <param name="schemaName">Schema name, if applicable.</param>
 95      /// <param name="tableName">Table name, if applicable.</param>
 96      /// <param name="columnName">Column name, if applicable.</param>
 97      /// <param name="datatypeName">The type name of data, if applicable.</param>
 98      /// <param name="constraintName">The constraint name, if applicable.</param>
 99      public PgSQLError(
 100         String severity,
 101         String code,
 102         String message,
 103         String detail,
 104         String hint,
 105         String position,
 106         String internalPosition,
 107         String internalQuery,
 108         String where,
 109         String file,
 110         String line,
 111         String routine,
 112         String schemaName,
 113         String tableName,
 114         String columnName,
 115         String datatypeName,
 116         String constraintName
 117         )
 118      {
 119         this.Severity = severity;
 120         this.Code = code;
 121         this.Message = message;
 122         this.Detail = detail;
 123         this.Hint = hint;
 124         this.Position = position;
 125         this.InternalPosition = internalPosition;
 126         this.InternalQuery = internalQuery;
 127         this.Where = where;
 128         this.File = file;
 129         this.Line = line;
 130         this.Routine = routine;
 131         this.SchemaName = schemaName;
 132         this.TableName = tableName;
 133         this.ColumnName = columnName;
 134         this.DatatypeName = datatypeName;
 135         this.ConstraintName = constraintName;
 136      }
 137
 138      /// <summary>
 139      /// Gets the error severity.
 140      /// </summary>
 141      /// <value>The error severity.</value>
 142      public String Severity { get; }
 143
 144      /// <summary>
 145      /// Gets the error code.
 146      /// </summary>
 147      /// <value>The error code.</value>
 148      public String Code { get; }
 149
 150      /// <summary>
 151      /// Gets the informative error message.
 152      /// </summary>
 153      /// <value>The informative error message.</value>
 154      public String Message { get; }
 155
 156      /// <summary>
 157      /// Gets the information about the details of error.
 158      /// </summary>
 159      /// <value>The information about the details of error.</value>
 160      public String Detail { get; }
 161
 162      /// <summary>
 163      /// Gets any possible hint about detecting or avoiding an error.
 164      /// </summary>
 165      /// <value>Any possible hint about detecting or avoiding an error.</value>
 166      public String Hint { get; }
 167
 168      /// <summary>
 169      /// Gets the position information in SQL code.
 170      /// </summary>
 171      /// <value>The position information in SQL code.</value>
 172      public String Position { get; }
 173
 174      /// <summary>
 175      /// Gets the internal position information in source code.
 176      /// </summary>
 177      /// <value>The internal position information in source code.</value>
 178      public String InternalPosition { get; }
 179
 180      /// <summary>
 181      /// Gets the internal query.
 182      /// </summary>
 183      /// <value>The internal query.</value>
 184      public String InternalQuery { get; }
 185
 186      /// <summary>
 187      /// Gets the function information in source code where error occurred.
 188      /// </summary>
 189      /// <value>The function information in source code where error occurred.</value>
 190      public String Where { get; }
 191
 192      /// <summary>
 193      /// Gets the file name of source code where error occurred.
 194      /// </summary>
 195      /// <value>The file name of source code where error occurred.</value>
 196      public String File { get; }
 197
 198      /// <summary>
 199      /// Gets the line number of source code file where error occurred.
 200      /// </summary>
 201      /// <value>The line number of source code file where error occurred.</value>
 202      public String Line { get; }
 203
 204      /// <summary>
 205      /// Gets the routine name, if applicable.
 206      /// </summary>
 207      /// <value>The routine name, if applicable.</value>
 208      public String Routine { get; }
 209
 210      /// <summary>
 211      /// Gets the schema name, if applicable.
 212      /// </summary>
 213      /// <value>The schema name, if applicable.</value>
 214      public String SchemaName { get; }
 215
 216      /// <summary>
 217      /// Gets the table name, if applicable.
 218      /// </summary>
 219      /// <value>The table name, if applicable.</value>
 220      public String TableName { get; }
 221
 222      /// <summary>
 223      /// Gets the column name, if applicable.
 224      /// </summary>
 225      /// <value>The column name, if applicable.</value>
 226      public String ColumnName { get; }
 227
 228      /// <summary>
 229      /// Gets the type name of data, if applicable.
 230      /// </summary>
 231      /// <value>The type name of data, if applicable.</value>
 232      public String DatatypeName { get; }
 233
 234      /// <summary>
 235      /// Gets the constraint name, if applicable.
 236      /// </summary>
 237      /// <value>The constraint name, if applicable.</value>
 238      public String ConstraintName { get; }
 239
 240      /// <summary>
 241      /// Overrides <see cref="Object.ToString"/> to provide simple textual description of the object.
 242      /// </summary>
 243      /// <returns>The <see cref="Message"/>, followed by <see cref="Severity"/> if it is not <c>null</c> and not empty,
 244      public override String ToString()
 245      {
 246         var sb = new StringBuilder();
 247
 248         sb.Append( this.Message );
 249
 250         if ( !String.IsNullOrEmpty( this.Severity ) )
 251         {
 252            sb.Append( ", Severity: " ).Append( this.Severity );
 253         }
 254         if ( !String.IsNullOrEmpty( this.Code ) )
 255         {
 256            sb.Append( ", Code: " ).Append( this.Code );
 257         }
 258         if ( !String.IsNullOrEmpty( this.Hint ) )
 259         {
 260            sb.Append( ", Hint: " ).Append( this.Hint );
 261         }
 262
 263         return sb.ToString();
 264      }
 265   }
 266}
 267
 268/// <summary>
 269/// This class contains extension methods for types defined in this assembly.
 270/// </summary>
 271public static partial class E_CBAM
 272{
 273   /// <summary>
 274   /// Helper method to check whether this <see cref="PgSQLException"/> has any given error codes.
 275   /// </summary>
 276   /// <param name="exception">This <see cref="PgSQLException"/>.</param>
 277   /// <param name="codes">The codes to check.</param>
 278   /// <returns><c>true</c> if this <see cref="PgSQLException"/> is not <c>null</c>, and has at least one <see cref="PgS
 279   public static Boolean HasErrorCodes( this PgSQLException exception, params String[] codes )
 280   {
 0281      return exception != null
 0282         && !codes.IsNullOrEmpty()
 0283         && exception.Errors.Length > 0
 0284         && exception.Errors.Any( e => codes.Any( c => String.Equals( e.Code, c, StringComparison.OrdinalIgnoreCase ) ) 
 285   }
 286}

/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL/TypeRegistry.cs

#LineLine coverage
 1/*
 2 * Copyright 2017 Stanislav Muhametsin. All rights Reserved.
 3 *
 4 * Licensed  under the  Apache License,  Version 2.0  (the "License");
 5 * you may not use  this file  except in  compliance with the License.
 6 * You may obtain a copy of the License at
 7 *
 8 *   http://www.apache.org/licenses/LICENSE-2.0
 9 *
 10 * Unless required by applicable law or agreed to in writing, software
 11 * distributed  under the  License is distributed on an "AS IS" BASIS,
 12 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
 13 * implied.
 14 *
 15 * See the License for the specific language governing permissions and
 16 * limitations under the License.
 17 */
 18using CBAM.Abstractions;
 19using CBAM.SQL.PostgreSQL;
 20using System;
 21using System.Collections.Generic;
 22using System.IO;
 23using System.Reflection;
 24using System.Text;
 25using System.Threading;
 26using System.Threading.Tasks;
 27using UtilPack;
 28
 29
 30namespace CBAM.SQL.PostgreSQL
 31{
 32
 33   using TSyncTextualSizeInfo = ValueTuple<Int32, String>;
 34
 35   /// <summary>
 36   /// This interface allows customization as to how <see cref="PgSQLConnection"/> handles SQL and CLR types and mapping
 37   /// </summary>
 38   /// <seealso cref="PgSQLConnection.TypeRegistry"/>
 39   /// <seealso cref="PgSQLTypeFunctionality"/>
 40   public interface TypeRegistry
 41   {
 42      /// <summary>
 43      /// Asynchronously adds functionality for given types so that the <see cref="PgSQLConnection"/> of this <see cref=
 44      /// </summary>
 45      /// <param name="typeFunctionalityInfos">The information about type functionalities, containing the type name in t
 46      /// <returns>Asynchronously returns the amount of functionalities actually processed. Any functionality informatio
 47      /// <exception cref="PgSQLException">If some error occurs during querying type IDs from database.</exception>
 48      ValueTask<Int32> AddTypeFunctionalitiesAsync( params (String DBTypeName, Type CLRType, Func<PgSQLTypeDatabaseData,
 49
 50      /// <summary>
 51      /// Tries to get <see cref="TypeFunctionalityInformation"/> for given type ID.
 52      /// </summary>
 53      /// <param name="typeID">The type ID, as stored in the database this connection is connected to.</param>
 54      /// <returns>A <see cref="TypeFunctionalityInformation"/> for given type ID, or <c>null</c> if type information fo
 55      TypeFunctionalityInformation TryGetTypeInfo( Int32 typeID );
 56
 57      /// <summary>
 58      /// Tries to get <see cref="TypeFunctionalityInformation"/> for given CLR type.
 59      /// </summary>
 60      /// <param name="clrType">The CLR <see cref="Type"/>.</param>
 61      /// <returns>A <see cref="TypeFunctionalityInformation"/> for given CLR type, or <c>null</c> if type information f
 62      /// <remarks>
 63      /// The <see cref="TypeFunctionalityInformation.CLRType"/> property of returned <see cref="TypeFunctionalityInform
 64      /// </remarks>
 65      TypeFunctionalityInformation TryGetTypeInfo( Type clrType );
 66
 67   }
 68
 69   //public struct TypeFunctionalityCreationParameters
 70   //{
 71   //   public TypeFunctionalityCreationParameters(
 72   //      TypeRegistry typeRegistry,
 73   //      PgSQLTypeDatabaseData databaseData
 74   //      )
 75   //   {
 76   //      this.TypeRegistry = ArgumentValidator.ValidateNotNull( nameof( typeRegistry ), typeRegistry );
 77   //      this.DatabaseData = ArgumentValidator.ValidateNotNull( nameof( databaseData ), databaseData );
 78   //   }
 79
 80   //   public TypeRegistry TypeRegistry { get; }
 81   //   public PgSQLTypeDatabaseData DatabaseData { get; }
 82   //}
 83
 84   /// <summary>
 85   /// This type is used as return type for callback which adds custom type functionality via <see cref="TypeRegistry.Ad
 86   /// </summary>
 87   public struct TypeFunctionalityCreationResult
 88   {
 89      /// <summary>
 90      /// Creates a new <see cref="TypeFunctionalityCreationResult"/> with given parameters.
 91      /// </summary>
 92      /// <param name="functionality">The <see cref="PgSQLTypeFunctionality"/> object.</param>
 93      /// <param name="isDefaultForCLRType">Whether the <paramref name="functionality"/> is the default for CLR type it 
 94      /// <exception cref="ArgumentNullException">If <paramref name="functionality"/> is <c>null</c>.</exception>
 95      public TypeFunctionalityCreationResult(
 96         PgSQLTypeFunctionality functionality,
 97         Boolean isDefaultForCLRType
 98         )
 99      {
 100         this.TypeFunctionality = ArgumentValidator.ValidateNotNull( nameof( functionality ), functionality );
 101         this.IsDefaultForCLRType = isDefaultForCLRType;
 102      }
 103
 104      /// <summary>
 105      /// Gets the <see cref="PgSQLTypeFunctionality"/> object.
 106      /// </summary>
 107      /// <value>The <see cref="PgSQLTypeFunctionality"/> object.</value>
 108      /// <seealso cref="PgSQLTypeFunctionality"/>
 109      public PgSQLTypeFunctionality TypeFunctionality { get; }
 110
 111      /// <summary>
 112      /// Gets the value indicating whether <see cref="TypeFunctionality"/> is the default for CLR type it represents.
 113      /// </summary>
 114      /// <value>The value indicating whether <see cref="TypeFunctionality"/> is the default for CLR type it represents.
 115      public Boolean IsDefaultForCLRType { get; }
 116   }
 117
 118   /// <summary>
 119   /// This class contains all information about a single mapping between PostgreSQL type and CLR type.
 120   /// </summary>
 121   public class TypeFunctionalityInformation
 122   {
 123      /// <summary>
 124      /// Creates a new instance of <see cref="TypeFunctionalityInformation"/> with given parameters.
 125      /// </summary>
 126      /// <param name="clrType">The CLR <see cref="Type"/> that <paramref name="functionality"/> supports.</param>
 127      /// <param name="functionality">The <see cref="PgSQLTypeFunctionality"/> for this type information.</param>
 128      /// <param name="databaseData">The <see cref="PgSQLTypeDatabaseData"/> containing type name and type ID for this t
 129      /// <remarks>The constructor is intended to be used mainly by <see cref="TypeRegistry"/> implementations.</remarks
 130      /// <exception cref="ArgumentNullException">If any of <paramref name="clrType"/>, <paramref name="functionality"/>
 131      public TypeFunctionalityInformation(
 132         Type clrType,
 133         PgSQLTypeFunctionality functionality,
 134         PgSQLTypeDatabaseData databaseData
 135         )
 136      {
 137         this.CLRType = ArgumentValidator.ValidateNotNull( nameof( clrType ), clrType );
 138         this.Functionality = ArgumentValidator.ValidateNotNull( nameof( functionality ), functionality );
 139         this.DatabaseData = ArgumentValidator.ValidateNotNull( nameof( databaseData ), databaseData );
 140      }
 141
 142      /// <summary>
 143      /// Gets the CLR <see cref="Type"/> of this type information.
 144      /// </summary>
 145      /// <value>The CLR <see cref="Type"/> of this type information.</value>
 146      public Type CLRType { get; }
 147
 148      /// <summary>
 149      /// Gets the <see cref="PgSQLTypeFunctionality"/> for this type information.
 150      /// </summary>
 151      /// <value>The <see cref="PgSQLTypeFunctionality"/> for this type information.</value>
 152      /// <seealso cref="PgSQLTypeFunctionality"/>
 153      public PgSQLTypeFunctionality Functionality { get; }
 154
 155      /// <summary>
 156      /// Gets the <see cref="PgSQLTypeDatabaseData"/> for this type information.
 157      /// This data contains the type name in the database, along with type ID (<c>oid</c>).
 158      /// </summary>
 159      /// <value>The <see cref="PgSQLTypeDatabaseData"/> for this type information.</value>
 160      /// <seealso cref="PgSQLTypeDatabaseData"/>
 161      public PgSQLTypeDatabaseData DatabaseData { get; }
 162   }
 163
 164   /// <summary>
 165   /// This interface contains all the API required by implementation of <see cref="PgSQLConnection"/> to serialize and 
 166   /// Objects implementing this interface are registered to <see cref="TypeRegistry"/> of the single <see cref="PgSQLCo
 167   /// </summary>
 168   /// <seealso cref="TypeRegistry"/>
 169   /// <seealso href="https://www.postgresql.org/docs/current/static/protocol.html"/>
 170   public interface PgSQLTypeFunctionality
 171   {
 172      /// <summary>
 173      /// Gets the value indicating whether this <see cref="PgSQLTypeFunctionality"/> supports reading the binary data f
 174      /// </summary>
 175      /// <value>The value indicating whether this <see cref="PgSQLTypeFunctionality"/> supports reading the binary data
 176      /// <seealso cref="DataFormat"/>
 177      Boolean SupportsReadingBinaryFormat { get; }
 178
 179      /// <summary>
 180      /// Gets the value indicating whether this <see cref="PgSQLTypeFunctionality"/> supports writing the binary data f
 181      /// </summary>
 182      /// <value>The value indicating whether this <see cref="PgSQLTypeFunctionality"/> supports writing the binary data
 183      /// <seealso cref="DataFormat"/>
 184      Boolean SupportsWritingBinaryFormat { get; }
 185
 186      /// <summary>
 187      /// Asynchronously performs deserializing of the value sent by backend into CLR object.
 188      /// </summary>
 189      /// <param name="dataFormat">The <see cref="DataFormat"/> the value is being sent by backend.</param>
 190      /// <param name="boundData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specif
 191      /// <param name="helper">The <see cref="BackendABIHelper"/> application binary interface helper.</param>
 192      /// <param name="stream">The <see cref="StreamReaderWithResizableBufferAndLimitedSize"/> to use to read binary dat
 193      /// <returns>Asynchronously returns the CLR object deserialized from <paramref name="stream"/>.</returns>
 194      ValueTask<Object> ReadBackendValueAsync(
 195         DataFormat dataFormat,
 196         PgSQLTypeDatabaseData boundData,
 197         BackendABIHelper helper,
 198         StreamReaderWithResizableBufferAndLimitedSize stream
 199         );
 200
 201      /// <summary>
 202      /// Gets the size of value recognized by this <see cref="PgSQLTypeFunctionality"/>, in bytes.
 203      /// </summary>
 204      /// <param name="dataFormat">The <see cref="DataFormat"/> value is being sent to backend.</param>
 205      /// <param name="boundData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specif
 206      /// <param name="helper">The <see cref="BackendABIHelper"/> application binary interface helper.</param>
 207      /// <param name="value">The value recognized by this <see cref="PgSQLTypeFunctionality"/>.</param>
 208      /// <param name="isArrayElement">Whether the <paramref name="value"/> is being sent inside SQL array.</param>
 209      /// <returns>The <see cref="BackendSizeInfo"/> object containing the byte count and optional custom information.</
 210      /// <seealso cref="DataFormat"/>
 211      /// <exception cref="ArgumentNullException">If <paramref name="value"/> is <c>null</c>.</exception>
 212      BackendSizeInfo GetBackendSize(
 213         DataFormat dataFormat,
 214         PgSQLTypeDatabaseData boundData,
 215         BackendABIHelper helper,
 216         Object value,
 217         Boolean isArrayElement
 218         );
 219
 220      /// <summary>
 221      /// Asynchronously performs serializing of the CLR object into binary data sent to backend.
 222      /// </summary>
 223      /// <param name="dataFormat">The <see cref="DataFormat"/> of the data, as expected by backend.</param>
 224      /// <param name="boundData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specif
 225      /// <param name="helper">The <see cref="BackendABIHelper"/> application binary interface helper.</param>
 226      /// <param name="stream">The <see cref="StreamWriterWithResizableBufferAndLimitedSize"/> to write binary data to.<
 227      /// <param name="value">The CLR object to serialize.</param>
 228      /// <param name="additionalInfoFromSize">The the <see cref="BackendSizeInfo"/>, as returned by <see cref="GetBacke
 229      /// <param name="isArrayElement">Whether <paramref name="value"/> is being sent inside SQL array.</param>
 230      /// <returns>Asychronously returns after the <paramref name="value"/> has been serialized.</returns>
 231      /// <seealso cref="DataFormat"/>
 232      /// <exception cref="ArgumentNullException">If <paramref name="value"/> is <c>null</c>.</exception>
 233      Task WriteBackendValueAsync(
 234         DataFormat dataFormat,
 235         PgSQLTypeDatabaseData boundData,
 236         BackendABIHelper helper,
 237         StreamWriterWithResizableBufferAndLimitedSize stream,
 238         Object value,
 239         BackendSizeInfo additionalInfoFromSize,
 240         Boolean isArrayElement
 241         );
 242
 243      /// <summary>
 244      /// Tries to change some object to the type recognized by this <see cref="PgSQLTypeFunctionality"/>.
 245      /// </summary>
 246      /// <param name="dbData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specific 
 247      /// <param name="obj">The object to change type to type recognized by this <see cref="PgSQLTypeFunctionality"/>.</
 248      /// <returns>The object of type recognized by this <see cref="PgSQLTypeFunctionality"/>.</returns>
 249      /// <exception cref="ArgumentNullException">If <paramref name="obj"/> is <c>null</c>.</exception>
 250      /// <exception cref="InvalidCastException">If this <see cref="PgSQLTypeFunctionality"/> does not know how to chang
 251      Object ChangeTypeFrameworkToPgSQL( PgSQLTypeDatabaseData dbData, Object obj );
 252
 253      /// <summary>
 254      /// Changes the object deserialized by <see cref="ReadBackendValueAsync"/> method to another type.
 255      /// </summary>
 256      /// <param name="dbData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specific 
 257      /// <param name="obj">The object to change type.</param>
 258      /// <param name="typeTo">The type to change <paramref name="obj"/> to.</param>
 259      /// <returns>The object of given type.</returns>
 260      /// <exception cref="ArgumentNullException">If <paramref name="obj"/> is <c>null</c>.</exception>
 261      /// <exception cref="InvalidCastException">If this <see cref="PgSQLTypeFunctionality"/> does not know how to chang
 262      Object ChangeTypePgSQLToFramework( PgSQLTypeDatabaseData dbData, Object obj, Type typeTo );
 263   }
 264
 265   /// <summary>
 266   /// This structure contains information about the size of some object when it is being sent to PostgreSQL backend.
 267   /// The method <see cref="PgSQLTypeFunctionality.GetBackendSize"/> uses this structure as return type.
 268   /// </summary>
 269   public struct BackendSizeInfo
 270   {
 271      /// <summary>
 272      /// Creates a new instance of <see cref="BackendSizeInfo"/> with given parameters.
 273      /// </summary>
 274      /// <param name="byteCount">The amount of bytes that the object being serialized will take.</param>
 275      /// <param name="customInformation">Optional custom information to pass to <see cref="PgSQLTypeFunctionality.Write
 276      public BackendSizeInfo(
 277         Int32 byteCount,
 278         Object customInformation = null
 279         )
 280      {
 281         this.ByteCount = byteCount;
 282         this.CustomInformation = customInformation;
 283      }
 284
 285      /// <summary>
 286      /// Gets the amount of bytes that the object being serialized will take.
 287      /// </summary>
 288      /// <value>The amount of bytes that the object being serialized will take.</value>
 289      public Int32 ByteCount { get; }
 290
 291      /// <summary>
 292      /// Gets optional custom information to pass to <see cref="PgSQLTypeFunctionality.WriteBackendValueAsync"/> method
 293      /// </summary>
 294      /// <value>Optional custom information to pass to <see cref="PgSQLTypeFunctionality.WriteBackendValueAsync"/> meth
 295      public Object CustomInformation { get; }
 296   }
 297
 298   /// <summary>
 299   /// This class contains useful utilities and methods used by <see cref="PgSQLTypeFunctionality"/> objects when they s
 300   /// </summary>
 301   public class BackendABIHelper
 302   {
 303      private readonly BinaryStringPool _stringPool;
 304
 305      /// <summary>
 306      /// Creates a new instance of <see cref="BackendABIHelper"/> with given parameters.
 307      /// </summary>
 308      /// <param name="encoding">The <see cref="IEncodingInfo"/> to use.</param>
 309      /// <param name="stringPool">The <see cref="BinaryStringPool"/> to use.</param>
 310      /// <exception cref="ArgumentNullException">If <paramref name="encoding"/> or <paramref name="stringPool"/> is <c>
 311      public BackendABIHelper(
 312         IEncodingInfo encoding,
 313         BinaryStringPool stringPool
 314         )
 315      {
 316         this._stringPool = ArgumentValidator.ValidateNotNull( nameof( stringPool ), stringPool );
 317         this.CharacterReader = new StreamCharacterReaderLogic( encoding );
 318         this.CharacterWriter = new StreamCharacterWriterLogic( encoding, 1024 );
 319      }
 320
 321      /// <summary>
 322      /// Gets the <see cref="IEncodingInfo"/> of this connection.
 323      /// </summary>
 324      /// <value>The <see cref="IEncodingInfo"/> of this connection.</value>
 325      public IEncodingInfo Encoding
 326      {
 327         get
 328         {
 329            return this.CharacterReader.Encoding;
 330         }
 331      }
 332
 333      /// <summary>
 334      /// Gets the <see cref="StreamCharacterReaderLogic"/> of this connection.
 335      /// </summary>
 336      /// <value>The <see cref="StreamCharacterReaderLogic"/> of this connection.</value>
 337      public StreamCharacterReaderLogic CharacterReader { get; }
 338
 339      /// <summary>
 340      /// Gets the <see cref="StreamCharacterWriterLogic"/> of this connection.
 341      /// </summary>
 342      /// <value>The <see cref="StreamCharacterWriterLogic"/> of this connection.</value>
 343      public StreamCharacterWriterLogic CharacterWriter { get; }
 344
 345      /// <summary>
 346      /// Gets pooled string or deserializes from given binary data and pools the string.
 347      /// </summary>
 348      /// <param name="array">The binary data.</param>
 349      /// <param name="offset">The offset in <paramref name="array"/> where to start reading data.</param>
 350      /// <param name="count">The amount of bytes to read in <paramref name="array"/>.</param>
 351      /// <returns>Pooled or deserialized string.</returns>
 352      public String GetStringWithPool( Byte[] array, Int32 offset, Int32 count )
 353      {
 354         return this._stringPool.GetString( array, offset, count );
 355      }
 356
 357   }
 358
 359   /// <summary>
 360   /// This enumeration describes the data format used when (de)serializing CLR objects from and to the backend.
 361   /// </summary>
 362   /// <seealso href="https://www.postgresql.org/docs/current/static/protocol-overview.html#PROTOCOL-FORMAT-CODES"/>
 363   public enum DataFormat : short
 364   {
 365      /// <summary>
 366      /// This value signifies that the binary data is in text format.
 367      /// </summary>
 368      Text = 0,
 369
 370      /// <summary>
 371      /// This value signifies that the binary data is in binary format.
 372      /// </summary>
 373      Binary = 1,
 374   }
 375
 376   /// <summary>
 377   /// This is utility class containing some useful and common information when (de)serializing CLR objects from and to 
 378   /// </summary>
 379   public abstract class CommonPgSQLTypeFunctionalityInfo
 380   {
 381      static CommonPgSQLTypeFunctionalityInfo()
 382      {
 383         var format = (System.Globalization.NumberFormatInfo) System.Globalization.CultureInfo.InvariantCulture.NumberFo
 384         format.NumberDecimalDigits = 15;
 385         NumberFormat = format;
 386      }
 387
 388      /// <summary>
 389      /// Gets the <see cref="System.Globalization.NumberFormatInfo"/> to use when (de)serializing numerical values.
 390      /// </summary>
 391      /// <value>The <see cref="System.Globalization.NumberFormatInfo"/> to use when (de)serializing numerical values.</
 392      public static System.Globalization.NumberFormatInfo NumberFormat { get; }
 393
 394   }
 395
 396   /// <summary>
 397   /// This class implements <see cref="PgSQLTypeFunctionality"/> by redirecting all methods to callbacks given to const
 398   /// </summary>
 399   /// <typeparam name="TValue">The type supported by this <see cref="DefaultPgSQLTypeFunctionality{TValue}"/>.</typepar
 400   /// <remarks>
 401   /// Usually, the <see cref="CreateSingleBodyUnboundInfo"/> method is used to create instances of this class.
 402   /// </remarks>
 403   public class DefaultPgSQLTypeFunctionality<TValue> : PgSQLTypeFunctionality
 404   {
 405
 406
 407      private readonly ReadFromBackend<TValue> _text2CLR;
 408      private readonly ReadFromBackend<TValue> _binary2CLR;
 409      private readonly ChangePgSQLToSystem<TValue> _pg2System;
 410      private readonly ChangeSystemToPgSQL<TValue> _system2PG;
 411      private readonly CalculateBackendSize<TValue, BackendSizeInfo> _clr2BinarySize;
 412      private readonly WriteToBackend<TValue> _clr2Binary;
 413      private readonly CalculateBackendSize<TValue, BackendSizeInfo> _clr2TextSize;
 414      private readonly WriteToBackend<TValue> _clr2Text;
 415
 416      /// <summary>
 417      /// Creates a new instance of <see cref="DefaultPgSQLTypeFunctionality{TValue}"/> with given callbacks.
 418      /// Note that usually <see cref="CreateSingleBodyUnboundInfo"/> method is used to create new instance, but in case
 419      /// </summary>
 420      /// <param name="text2CLR">The callback used by <see cref="ReadBackendValueAsync"/> method when the <see cref="Dat
 421      /// <param name="binary2CLR">The callback used by <see cref="ReadBackendValueAsync"/> method when the <see cref="D
 422      /// <param name="clr2TextSize">The callback used by <see cref="GetBackendSize"/> method when the <see cref="DataFo
 423      /// <param name="clr2BinarySize">The callback used by <see cref="GetBackendSize"/> method when the <see cref="Data
 424      /// <param name="clr2Text">The callback used by <see cref="WriteBackendValueAsync"/> method when the <see cref="Da
 425      /// <param name="clr2Binary">The callback used by <see cref="WriteBackendValueAsync"/> method when the <see cref="
 426      /// <param name="pgSQL2System">The callack used by <see cref="ChangeTypePgSQLToFramework"/> method. If <c>null</c>
 427      /// <param name="system2PgSQL">The callback used by <see cref="ChangeTypeFrameworkToPgSQL"/> method. If <c>null</c
 428      public DefaultPgSQLTypeFunctionality(
 429         ReadFromBackend<TValue> text2CLR,
 430         ReadFromBackend<TValue> binary2CLR,
 431         CalculateBackendSize<TValue, BackendSizeInfo> clr2TextSize,
 432         CalculateBackendSize<TValue, BackendSizeInfo> clr2BinarySize,
 433         WriteToBackend<TValue> clr2Text,
 434         WriteToBackend<TValue> clr2Binary,
 435         ChangePgSQLToSystem<TValue> pgSQL2System,
 436         ChangeSystemToPgSQL<TValue> system2PgSQL
 437         )
 438      {
 439
 440         this._text2CLR = text2CLR;
 441         this._binary2CLR = binary2CLR;
 442         this._pg2System = pgSQL2System;
 443         this._system2PG = system2PgSQL;
 444         this._clr2BinarySize = clr2BinarySize;
 445         this._clr2Binary = clr2Binary;
 446         this._clr2TextSize = clr2TextSize;
 447         this._clr2Text = clr2Text;
 448      }
 449
 450      /// <summary>
 451      /// Implements <see cref="PgSQLTypeFunctionality.SupportsReadingBinaryFormat"/> by checking whether the appropriat
 452      /// </summary>
 453      /// <value>Value indicating whether appropriate <see cref="ReadFromBackend{TValue}"/> callback was given to constr
 454      public Boolean SupportsReadingBinaryFormat
 455      {
 456         get
 457         {
 458            return this._binary2CLR != null;
 459         }
 460      }
 461
 462      /// <summary>
 463      /// Implements <see cref="PgSQLTypeFunctionality.SupportsWritingBinaryFormat"/> by checking whether the appropriat
 464      /// </summary>
 465      /// <value>Value indicating whether appropriate <see cref="CalculateBackendSize{TValue, TResult}"/> and <see cref=
 466      public Boolean SupportsWritingBinaryFormat
 467      {
 468         get
 469         {
 470            return this._clr2BinarySize != null && this._clr2Binary != null;
 471         }
 472      }
 473
 474      /// <summary>
 475      /// Implements <see cref="PgSQLTypeFunctionality.ChangeTypePgSQLToFramework"/> by either calling the <see cref="Ch
 476      /// </summary>
 477      /// <param name="dbData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specific 
 478      /// <param name="obj">The object to change type.</param>
 479      /// <param name="typeTo">The type to change <paramref name="obj"/> to.</param>
 480      /// <returns>The object of given type.</returns>
 481      /// <exception cref="ArgumentNullException">If <paramref name="obj"/> is <c>null</c>.</exception>
 482      /// <exception cref="InvalidCastException">If this <see cref="PgSQLTypeFunctionality"/> does not know how to chang
 483      public Object ChangeTypePgSQLToFramework( PgSQLTypeDatabaseData dbData, Object obj, Type typeTo )
 484      {
 485         ArgumentValidator.ValidateNotNull( nameof( obj ), obj );
 486
 487         return this._pg2System == null ?
 488            Convert.ChangeType( obj, typeTo, System.Globalization.CultureInfo.InvariantCulture ) :
 489            this._pg2System( dbData, (TValue) obj, typeTo );
 490      }
 491
 492      /// <summary>
 493      /// Implements <see cref="PgSQLTypeFunctionality.ChangeTypeFrameworkToPgSQL"/> by either calling the <see cref="Ch
 494      /// </summary>
 495      /// <param name="dbData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specific 
 496      /// <param name="obj">The object to change type to type recognized by this <see cref="PgSQLTypeFunctionality"/>.</
 497      /// <returns>The object of type recognized by this <see cref="PgSQLTypeFunctionality"/>.</returns>
 498      /// <exception cref="ArgumentNullException">If <paramref name="obj"/> is <c>null</c>.</exception>
 499      /// <exception cref="InvalidCastException">If this <see cref="PgSQLTypeFunctionality"/> does not know how to chang
 500      public Object ChangeTypeFrameworkToPgSQL( PgSQLTypeDatabaseData dbData, Object obj )
 501      {
 502         ArgumentValidator.ValidateNotNull( nameof( obj ), obj );
 503
 504         return this._system2PG == null ?
 505            Convert.ChangeType( obj, typeof( TValue ), System.Globalization.CultureInfo.InvariantCulture ) :
 506            this._system2PG( dbData, obj );
 507      }
 508
 509      /// <summary>
 510      /// Implements <see cref="PgSQLTypeFunctionality.GetBackendSize"/> by calling appropriate <see cref="CalculateBack
 511      /// </summary>
 512      /// <param name="dataFormat">The <see cref="DataFormat"/> value is being sent to backend.</param>
 513      /// <param name="boundData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specif
 514      /// <param name="helper">The <see cref="BackendABIHelper"/> application binary interface helper.</param>
 515      /// <param name="value">The value recognized by this <see cref="PgSQLTypeFunctionality"/>.</param>
 516      /// <param name="isArrayElement">Whether the <paramref name="value"/> is being sent inside SQL array.</param>
 517      /// <returns>The <see cref="BackendSizeInfo"/> object containing the byte count and optional custom information.</
 518      /// <exception cref="ArgumentNullException">If <paramref name="value"/> is <c>null</c>.</exception>
 519      /// <exception cref="InvalidCastException">If <paramref name="value"/> is not of type <typeparamref name="TValue"/
 520      /// <exception cref="NotSupportedException">If given <paramref name="dataFormat"/> is not supported - either becau
 521      public BackendSizeInfo GetBackendSize( DataFormat dataFormat, PgSQLTypeDatabaseData boundData, BackendABIHelper he
 522      {
 523         ArgumentValidator.ValidateNotNull( nameof( value ), value );
 524         switch ( dataFormat )
 525         {
 526            case DataFormat.Text:
 527               return CheckDelegate( this._clr2TextSize, dataFormat )( boundData, helper.Encoding, (TValue) value, isArr
 528            case DataFormat.Binary:
 529               return CheckDelegate( this._clr2BinarySize, dataFormat )( boundData, helper.Encoding, (TValue) value, isA
 530            default:
 531               throw new NotSupportedException( $"Data format {dataFormat} is not recognized." );
 532         }
 533      }
 534
 535      /// <summary>
 536      /// Implements <see cref="PgSQLTypeFunctionality.WriteBackendValueAsync"/> by calling appropriate <see cref="Write
 537      /// </summary>
 538      /// <param name="dataFormat">The <see cref="DataFormat"/> of the data, as expected by backend.</param>
 539      /// <param name="boundData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specif
 540      /// <param name="helper">The <see cref="BackendABIHelper"/> application binary interface helper.</param>
 541      /// <param name="stream">The <see cref="StreamWriterWithResizableBufferAndLimitedSize"/> to write binary data to.<
 542      /// <param name="value">The CLR object to serialize.</param>
 543      /// <param name="additionalInfoFromSize">The the <see cref="BackendSizeInfo"/>, as returned by <see cref="GetBacke
 544      /// <param name="isArrayElement">Whether <paramref name="value"/> is being sent inside SQL array.</param>
 545      /// <returns>Asychronously returns after the <paramref name="value"/> has been serialized.</returns>
 546      /// <exception cref="ArgumentNullException">If <paramref name="value"/> is <c>null</c>.</exception>
 547      /// <exception cref="InvalidCastException">If <paramref name="value"/> is not of type <typeparamref name="TValue"/
 548      /// <exception cref="NotSupportedException">If given <paramref name="dataFormat"/> is not supported - either becau
 549      public Task WriteBackendValueAsync( DataFormat dataFormat, PgSQLTypeDatabaseData boundData, BackendABIHelper helpe
 550      {
 551         ArgumentValidator.ValidateNotNull( nameof( value ), value );
 552         switch ( dataFormat )
 553         {
 554            case DataFormat.Text:
 555               return CheckDelegate( this._clr2Text, dataFormat )( boundData, helper, stream, (TValue) value, additional
 556            case DataFormat.Binary:
 557               return CheckDelegate( this._clr2Binary, dataFormat )( boundData, helper, stream, (TValue) value, addition
 558            default:
 559               throw new NotSupportedException( $"Data format {dataFormat} is not recognized." );
 560         }
 561      }
 562
 563      /// <summary>
 564      /// Implements <see cref="PgSQLTypeFunctionality.ReadBackendValueAsync"/> by calling appropriate <see cref="ReadFr
 565      /// </summary>
 566      /// <param name="dataFormat">The <see cref="DataFormat"/> the value is being sent by backend.</param>
 567      /// <param name="boundData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specif
 568      /// <param name="helper">The <see cref="BackendABIHelper"/> application binary interface helper.</param>
 569      /// <param name="stream">The <see cref="StreamReaderWithResizableBufferAndLimitedSize"/> to use to read binary dat
 570      /// <returns>Asynchronously returns the CLR object deserialized from <paramref name="stream"/>.</returns>
 571      /// <exception cref="NotSupportedException">If given <paramref name="dataFormat"/> is not supported - either becau
 572      public async ValueTask<Object> ReadBackendValueAsync(
 573         DataFormat dataFormat,
 574         PgSQLTypeDatabaseData boundData,
 575         BackendABIHelper helper,
 576         StreamReaderWithResizableBufferAndLimitedSize stream
 577         )
 578      {
 579         switch ( dataFormat )
 580         {
 581            case DataFormat.Binary:
 582               return await CheckDelegate( this._binary2CLR, dataFormat )( boundData, helper, stream );
 583            case DataFormat.Text:
 584               return await CheckDelegate( this._text2CLR, dataFormat )( boundData, helper, stream );
 585            default:
 586               throw new NotSupportedException( $"Data format {dataFormat} is not recognized." );
 587         }
 588      }
 589
 590      private static T CheckDelegate<T>( T del, DataFormat dataFormat )
 591         where T : class
 592      {
 593         if ( del == null )
 594         {
 595            throw new NotSupportedException( $"The data format {dataFormat} is not supported." );
 596         }
 597         return del;
 598      }
 599
 600      /// <summary>
 601      /// Creates a new instance of <see cref="DefaultPgSQLTypeFunctionality{TValue}"/> which will read and write whole 
 602      /// </summary>
 603      /// <param name="text2CLR">Synchronous <see cref="ReadFromBackendSync{TValue}"/> callback to deserialize textual d
 604      /// <param name="binary2CLR">Synchronous <see cref="ReadFromBackendSync{TValue}"/> callback to deserialize binary 
 605      /// <param name="clr2TextSize">The <see cref="CalculateBackendSize{TValue, TResult}"/> callback to calculate byte 
 606      /// <param name="clr2BinarySize">The <see cref="CalculateBackendSize{TValue, TResult}"/> callback to calculate byt
 607      /// <param name="clr2Text">Synchronous <see cref="WriteToBackendSync{TValue, TResult}"/> callback to serialize CLR
 608      /// <param name="clr2Binary">Synchronous <see cref="WriteToBackendSync{TValue, TSizeInfo}"/> callback to serialize
 609      /// <param name="pgSQL2System">Callback to convert object of type <typeparamref name="TValue"/> into given type.</
 610      /// <param name="system2PgSQL">Callback to convert object into <typeparamref name="TValue"/>.</param>
 611      /// <returns>A new instance of <see cref="DefaultPgSQLTypeFunctionality{TValue}"/> which uses given callbacks to i
 612      /// <exception cref="ArgumentNullException">If <paramref name="text2CLR"/> is <c>null</c></exception>
 613      public static DefaultPgSQLTypeFunctionality<TValue> CreateSingleBodyUnboundInfo(
 614         ReadFromBackendSync<TValue> text2CLR,
 615         ReadFromBackendSync<TValue> binary2CLR,
 616         CalculateBackendSize<TValue, EitherOr<Int32, String>> clr2TextSize,
 617         CalculateBackendSize<TValue, Int32> clr2BinarySize,
 618         WriteToBackendSync<TValue, TSyncTextualSizeInfo> clr2Text,
 619         WriteToBackendSync<TValue, Int32> clr2Binary,
 620         ChangePgSQLToSystem<TValue> pgSQL2System,
 621         ChangeSystemToPgSQL<TValue> system2PgSQL
 622         )
 623      {
 624         ArgumentValidator.ValidateNotNull( nameof( text2CLR ), text2CLR );
 625
 626         CalculateBackendSize<TValue, BackendSizeInfo> textSizeActual;
 627         if ( clr2TextSize == null )
 628         {
 629            if ( typeof( IFormattable ).GetTypeInfo().IsAssignableFrom( typeof( TValue ).GetTypeInfo() ) )
 630            {
 631               textSizeActual = ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, TValue value, Boolean isArray
 632               {
 633                  var str = ( (IFormattable) value ).ToString( null, CommonPgSQLTypeFunctionalityInfo.NumberFormat );
 634                  return new BackendSizeInfo( encoding.Encoding.GetByteCount( str ), str );
 635               };
 636            }
 637            else
 638            {
 639               textSizeActual = ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, TValue value, Boolean isArray
 640               {
 641                  var str = value.ToString();
 642                  return new BackendSizeInfo( encoding.Encoding.GetByteCount( str ), str );
 643               };
 644            }
 645         }
 646         else
 647         {
 648            textSizeActual = ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, TValue value, Boolean isArrayEle
 649            {
 650               var thisTextSize = clr2TextSize( boundData, encoding, value, isArrayElement );
 651               return thisTextSize.IsFirst ? new BackendSizeInfo( thisTextSize.First ) : new BackendSizeInfo( encoding.E
 652            };
 653         }
 654
 655         WriteToBackendSync<TValue, TSyncTextualSizeInfo> clr2TextActual;
 656         if ( clr2Text == null )
 657         {
 658            clr2TextActual = ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, TValu
 659            {
 660               var str = additionalInfoFromSize.Item2;
 661               args.Encoding.Encoding.GetBytes( str, 0, str.Length, array, offset );
 662            };
 663         }
 664         else
 665         {
 666            clr2TextActual = clr2Text;
 667         }
 668
 669         return new DefaultPgSQLTypeFunctionality<TValue>(
 670            async ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, StreamReaderWithResizableBufferAndLimitedSiz
 671            {
 672               if ( stream != null )
 673               {
 674                  await stream.ReadAllBytesToBuffer();
 675               }
 676               return text2CLR( boundData, args, stream.Buffer, 0, (Int32) stream.TotalByteCount );
 677            },
 678            binary2CLR == null ? (ReadFromBackend<TValue>) null : async ( PgSQLTypeDatabaseData boundData, BackendABIHel
 679             {
 680                if ( stream != null )
 681                {
 682                   await stream.ReadAllBytesToBuffer();
 683                }
 684
 685                return binary2CLR( boundData, args, stream.Buffer, 0, (Int32) stream.TotalByteCount );
 686             },
 687            textSizeActual,
 688            clr2BinarySize == null ? (CalculateBackendSize<TValue, BackendSizeInfo>) null : ( PgSQLTypeDatabaseData boun
 689            async ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, StreamWriterWithResizableBufferAndLimitedSiz
 690            {
 691               (var offset, var count) = stream.ReserveBufferSegment( additionalInfoFromSize.ByteCount );
 692               clr2TextActual( boundData, args, stream.Buffer, offset, value, (additionalInfoFromSize.ByteCount, (String
 693               await stream.FlushAsync();
 694            },
 695            clr2Binary == null ? (WriteToBackend<TValue>) null : async ( PgSQLTypeDatabaseData boundData, BackendABIHelp
 696            {
 697               (var offset, var count) = stream.ReserveBufferSegment( additionalInfoFromSize.ByteCount );
 698               clr2Binary( boundData, args, stream.Buffer, offset, value, additionalInfoFromSize.ByteCount, isArrayEleme
 699               await stream.FlushAsync();
 700            },
 701            pgSQL2System,
 702            system2PgSQL
 703            );
 704      }
 705   }
 706
 707   /// <summary>
 708   /// This callback is used by <see cref="DefaultPgSQLTypeFunctionality{TValue}"/> in its <see cref="DefaultPgSQLTypeFu
 709   /// </summary>
 710   /// <typeparam name="TValue">The type of the value understood by <see cref="DefaultPgSQLTypeFunctionality{TValue}"/>.
 711   /// <param name="dbData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specific to 
 712   /// <param name="helper">The <see cref="BackendABIHelper"/> application binary interface helper.</param>
 713   /// <param name="stream">The <see cref="StreamReaderWithResizableBufferAndLimitedSize"/> to use to read binary data f
 714   /// <returns>Potentially asynchronously returns deserialized value from <paramref name="stream"/>.</returns>
 715   /// <remarks>
 716   /// The <see cref="DataFormat"/> is assumed to be known by this callback.
 717   /// </remarks>
 718   public delegate ValueTask<TValue> ReadFromBackend<TValue>( PgSQLTypeDatabaseData dbData, BackendABIHelper helper, Str
 719
 720   /// <summary>
 721   /// This callback is used by <see cref="DefaultPgSQLTypeFunctionality{TValue}"/> in its <see cref="DefaultPgSQLTypeFu
 722   /// </summary>
 723   /// <typeparam name="TValue">The type of the value understood by <see cref="DefaultPgSQLTypeFunctionality{TValue}"/>.
 724   /// <param name="dbData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specific to 
 725   /// <param name="pgSQLObject">The object to change type.</param>
 726   /// <param name="targetType">The type to change <paramref name="pgSQLObject"/> to.</param>
 727   /// <returns>The object of given type.</returns>
 728   /// <exception cref="ArgumentNullException">If <paramref name="pgSQLObject"/> is <c>null</c>.</exception>
 729   /// <exception cref="InvalidCastException">If this <see cref="PgSQLTypeFunctionality"/> does not know how to change t
 730   public delegate Object ChangePgSQLToSystem<TValue>( PgSQLTypeDatabaseData dbData, TValue pgSQLObject, Type targetType
 731
 732   /// <summary>
 733   /// This callback is used by <see cref="DefaultPgSQLTypeFunctionality{TValue}"/> in its <see cref="DefaultPgSQLTypeFu
 734   /// </summary>
 735   /// <typeparam name="TValue">The type of the value understood by <see cref="DefaultPgSQLTypeFunctionality{TValue}"/>.
 736   /// <param name="dbData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specific to 
 737   /// <param name="systemObject">The object to change type to type recognized by this <see cref="PgSQLTypeFunctionality
 738   /// <returns>The object of type recognized by this <see cref="PgSQLTypeFunctionality"/>.</returns>
 739   /// <exception cref="ArgumentNullException">If <paramref name="systemObject"/> is <c>null</c>.</exception>
 740   /// <exception cref="InvalidCastException">If this <see cref="PgSQLTypeFunctionality"/> does not know how to change t
 741   public delegate TValue ChangeSystemToPgSQL<TValue>( PgSQLTypeDatabaseData dbData, Object systemObject );
 742
 743   /// <summary>
 744   /// This callback is used by <see cref="DefaultPgSQLTypeFunctionality{TValue}"/> in its <see cref="DefaultPgSQLTypeFu
 745   /// </summary>
 746   /// <typeparam name="TValue">The type of the value understood by <see cref="DefaultPgSQLTypeFunctionality{TValue}"/>.
 747   /// <typeparam name="TResult">The type of calculation result. The <see cref="DefaultPgSQLTypeFunctionality{TValue}.Ge
 748   /// <param name="dbData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specific to 
 749   /// <param name="encoding">The <see cref="IEncodingInfo"/> used for text (de)serialization.</param>
 750   /// <param name="value">The value recognized by this <see cref="PgSQLTypeFunctionality"/>.</param>
 751   /// <param name="isArrayElement">Whether the <paramref name="value"/> is being sent inside SQL array.</param>
 752   /// <returns>The result of calculation.</returns>
 753   public delegate TResult CalculateBackendSize<TValue, TResult>( PgSQLTypeDatabaseData dbData, IEncodingInfo encoding, 
 754
 755   /// <summary>
 756   /// This callback is used by <see cref="DefaultPgSQLTypeFunctionality{TValue}"/> in its <see cref="DefaultPgSQLTypeFu
 757   /// </summary>
 758   /// <typeparam name="TValue">The type of the value understood by <see cref="DefaultPgSQLTypeFunctionality{TValue}"/>.
 759   /// <param name="dbData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specific to 
 760   /// <param name="helper">The <see cref="BackendABIHelper"/> application binary interface helper.</param>
 761   /// <param name="stream">The <see cref="StreamWriterWithResizableBufferAndLimitedSize"/> to write binary data to.</pa
 762   /// <param name="value">The CLR object to serialize.</param>
 763   /// <param name="additionalInfoFromSize">The the <see cref="BackendSizeInfo"/>, as returned by <see cref="CalculateBa
 764   /// <param name="isArrayElement">Whether <paramref name="value"/> is being sent inside SQL array.</param>
 765   /// <returns>Task which will complete once value has been written to <paramref name="stream"/>.</returns>
 766   public delegate Task WriteToBackend<TValue>( PgSQLTypeDatabaseData dbData, BackendABIHelper helper, StreamWriterWithR
 767
 768   /// <summary>
 769   /// This callback is used by <see cref="DefaultPgSQLTypeFunctionality{TValue}.CreateSingleBodyUnboundInfo"/>, to sync
 770   /// </summary>
 771   /// <typeparam name="TValue">The type of the value understood by <see cref="DefaultPgSQLTypeFunctionality{TValue}"/>.
 772   /// <param name="dbData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specific to 
 773   /// <param name="helper">The <see cref="BackendABIHelper"/> application binary interface helper.</param>
 774   /// <param name="array">The byte array containing whole data.</param>
 775   /// <param name="offset">The offset in <paramref name="array"/> where to start reading.</param>
 776   /// <param name="count">The amount of bytes to read from <paramref name="array"/>.</param>
 777   /// <returns>The deserialized value.</returns>
 778   public delegate TValue ReadFromBackendSync<TValue>( PgSQLTypeDatabaseData dbData, BackendABIHelper helper, Byte[] arr
 779
 780   /// <summary>
 781   /// This callback is used by <see cref="DefaultPgSQLTypeFunctionality{TValue}.CreateSingleBodyUnboundInfo"/>, to sync
 782   /// </summary>
 783   /// <typeparam name="TValue">The type of the value understood by <see cref="DefaultPgSQLTypeFunctionality{TValue}"/>.
 784   /// <typeparam name="TSizeInfo">The type of size information (return type of <see cref="CalculateBackendSize{TValue, 
 785   /// <param name="dbData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specific to 
 786   /// <param name="helper">The <see cref="BackendABIHelper"/> application binary interface helper.</param>
 787   /// <param name="array">The byte array to write data to.</param>
 788   /// <param name="offset">The offset in <paramref name="array"/> where to start writing.</param>
 789   /// <param name="value">The vlue to serialize.</param>
 790   /// <param name="additionalInfoFromSize">The result of calling the corresponding <see cref="CalculateBackendSize{TVal
 791   /// <param name="isArrayElement">Whether the <paramref name="value"/> is inside an SQL array.</param>
 792   public delegate void WriteToBackendSync<TValue, TSizeInfo>( PgSQLTypeDatabaseData dbData, BackendABIHelper helper, By
 793
 794   /// <summary>
 795   /// This class contains information contained within database of a single SQL type.
 796   /// </summary>
 797   public sealed class PgSQLTypeDatabaseData
 798   {
 799      /// <summary>
 800      /// Creates a new instance of <see cref="PgSQLTypeDatabaseData"/> with given parameters.
 801      /// </summary>
 802      /// <param name="typeName">The textual name of the SQL type.</param>
 803      /// <param name="typeID">The ID (<c>oid</c>) of the SQL type.</param>
 804      /// <param name="arrayDelimiter">The textual delimiter character when values of this SQL type are within array.</p
 805      /// <param name="elementTypeID">The ID (<c>oid</c>) of element type, if this SQL type is an array.</param>
 806      public PgSQLTypeDatabaseData(
 807         String typeName,
 808         Int32 typeID,
 809         String arrayDelimiter,
 810         Int32 elementTypeID
 811         )
 812      {
 813         this.TypeName = typeName;
 814         this.TypeID = typeID;
 815         this.ArrayDelimiter = arrayDelimiter;
 816         this.ElementTypeID = elementTypeID;
 817      }
 818
 819      /// <summary>
 820      /// Gets the textual name of the SQL type.
 821      /// </summary>
 822      /// <value>The textual name of the SQL type.</value>
 823      public String TypeName { get; }
 824
 825      /// <summary>
 826      /// Gets the ID (<c>oid</c>) of the SQL type.
 827      /// </summary>
 828      /// <value>The ID (<c>oid</c>) of the SQL type.</value>
 829      public Int32 TypeID { get; }
 830
 831      /// <summary>
 832      /// Gets the ID (<c>oid</c>) of element type, if this SQL type is an array.
 833      /// </summary>
 834      /// <value>The ID (<c>oid</c>) of element type, if this SQL type is an array.</value>
 835      public Int32 ElementTypeID { get; }
 836
 837      /// <summary>
 838      /// Gets the textual delimiter character when values of this SQL type are within array.
 839      /// </summary>
 840      /// <value>The textual delimiter character when values of this SQL type are within array.</value>
 841      /// <remarks>
 842      /// This is <see cref="String"/> instead of <see cref="Char"/> in case we get exotic stuff like surrogate pairs he
 843      /// </remarks>
 844      public String ArrayDelimiter { get; }
 845   }
 846
 847   /// <summary>
 848   /// This class contains PostgreSQL-related extensions for types defined outside this assembly.
 849   /// </summary>
 850   public static partial class CBAMExtensions
 851   {
 852
 853      /// <summary>
 854      /// Writes <see cref="Int32"/> value to this byte array, in endianness expected by PostgreSQL backend (big-endian)
 855      /// </summary>
 856      /// <param name="array">This <see cref="Byte"/> array.</param>
 857      /// <param name="index">The index in <paramref name="array"/> where to write the <paramref name="value"/>.</param>
 858      /// <param name="value">The <see cref="Int32"/> value to write.</param>
 859      /// <returns>The <paramref name="array"/>.</returns>
 860      /// <exception cref="NullReferenceException">If this <see cref="Byte"/> array is <c>null</c>.</exception>
 861      /// <exception cref="IndexOutOfRangeException">If <paramref name="index"/> is out of valid range.</exception>
 862      public static Byte[] WritePgInt32( this Byte[] array, ref Int32 index, Int32 value )
 863      {
 864         array.WriteInt32BEToBytes( ref index, value );
 865         return array;
 866      }
 867
 868      /// <summary>
 869      /// Reads <see cref="Int32"/> value from this byte array, in endianness specified by PostgreSQL backend (big-endie
 870      /// </summary>
 871      /// <param name="array">This <see cref="Byte"/> array.</param>
 872      /// <param name="index">The index in <paramref name="array"/> where to start reading for <see cref="Int32"/>value.
 873      /// <returns>Deserialized <see cref="Int32"/>.</returns>
 874      /// <exception cref="NullReferenceException">If this <see cref="Byte"/> array is <c>null</c>.</exception>
 875      /// <exception cref="IndexOutOfRangeException">If <paramref name="index"/> is out of valid range.</exception>
 876      public static Int32 ReadPgInt32( this Byte[] array, ref Int32 index )
 877      {
 878         return array.ReadInt32BEFromBytes( ref index );
 879      }
 880
 881   }
 882}
 883
 884public static partial class E_CBAM
 885{
 886   private const Int32 NULL_BYTE_COUNT = -1;
 887
 888   /// <summary>
 889   /// This is helper method to first read <see cref="Int32"/> as size of data incoming, and if it is greater or equal t
 890   /// </summary>
 891   /// <param name="typeFunctionality">This <see cref="PgSQLTypeFunctionality"/>.</param>
 892   /// <param name="dataFormat">The <see cref="DataFormat"/> the value is being sent by backend.</param>
 893   /// <param name="boundData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specific 
 894   /// <param name="helper">The <see cref="BackendABIHelper"/> application binary interface helper.</param>
 895   /// <param name="stream">The <see cref="StreamReaderWithResizableBufferAndLimitedSize"/> to use to read binary data f
 896   /// <returns>Asynchronously returns the CLR object deserialized from <paramref name="stream"/>, or <c>null</c>.</retu
 897   public static async ValueTask<(Object Value, Int32 BytesReadFromStream)> ReadBackendValueCheckNull(
 898      this PgSQLTypeFunctionality typeFunctionality,
 899      DataFormat dataFormat,
 900      PgSQLTypeDatabaseData boundData,
 901      BackendABIHelper helper,
 902      StreamReaderWithResizableBufferAndLimitedSize stream
 903      )
 904   {
 0905      await stream.ReadOrThrow( sizeof( Int32 ) );
 0906      var byteCount = 0;
 0907      var length = stream.Buffer.ReadPgInt32( ref byteCount );
 908      Object retVal;
 0909      if ( length >= 0 )
 910      {
 0911         byteCount += length;
 0912         using ( var limitedStream = stream.CreateWithLimitedSizeAndSharedBuffer( length ) )
 913         {
 914            try
 915            {
 0916               retVal = await typeFunctionality.ReadBackendValueAsync(
 0917                  dataFormat,
 0918                  boundData,
 0919                  helper,
 0920                  limitedStream
 0921                  );
 922            }
 923            finally
 924            {
 925               try
 926               {
 927                  // TODO this might not be necessary now with DisposeAsync delegate always called by AsyncEnumerator...
 0928                  await limitedStream.SkipThroughRemainingBytes();
 0929               }
 0930               catch
 931               {
 932                  // Ignore
 0933               }
 934            }
 0935         }
 0936      }
 937      else
 938      {
 0939         retVal = null;
 940      }
 941
 0942      return (retVal, byteCount);
 0943   }
 944
 945   /// <summary>
 946   /// This is helper method to first check whether given value is <c>null</c>, and then return <c>-1</c>, or invoke <se
 947   /// </summary>
 948   /// <param name="typeFunctionality">This <see cref="PgSQLTypeFunctionality"/>.</param>
 949   /// <param name="dataFormat">The <see cref="DataFormat"/> value is being sent to backend.</param>
 950   /// <param name="dbData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specific to 
 951   /// <param name="helper">The <see cref="BackendABIHelper"/> application binary interface helper.</param>
 952   /// <param name="value">The value recognized by this <see cref="PgSQLTypeFunctionality"/>.</param>
 953   /// <param name="isArrayElement">Whether the <paramref name="value"/> is being sent inside SQL array.</param>
 954   /// <returns>The <see cref="BackendSizeInfo"/> object containing the byte count and optional custom information. Will
 955   public static BackendSizeInfo GetBackendSizeCheckNull( this PgSQLTypeFunctionality typeFunctionality, DataFormat data
 956   {
 94957      return value == null ? new BackendSizeInfo( NULL_BYTE_COUNT ) : typeFunctionality.GetBackendSize( dataFormat, dbDa
 958   }
 959
 960   /// <summary>
 961   /// This is helper method to first write the <see cref="BackendSizeInfo.ByteCount"/> property of <see cref="BackendSi
 962   /// </summary>
 963   /// <param name="typeFunctionality">This <see cref="PgSQLTypeFunctionality"/>.</param>
 964   /// <param name="dataFormat">The <see cref="DataFormat"/> of the data, as expected by backend.</param>
 965   /// <param name="boundData">The <see cref="PgSQLTypeDatabaseData"/> containing information about this type, specific 
 966   /// <param name="helper">The <see cref="BackendABIHelper"/> application binary interface helper.</param>
 967   /// <param name="stream">The <see cref="StreamWriterWithResizableBufferAndLimitedSize"/> to write binary data to.</pa
 968   /// <param name="value">The CLR object to serialize.</param>
 969   /// <param name="additionalInfoFromSize">The the <see cref="BackendSizeInfo"/>, as returned by <see cref="PgSQLTypeFu
 970   /// <param name="isArrayElement">Whether <paramref name="value"/> is being sent inside SQL array.</param>
 971   /// <returns>Asychronously returns after the <paramref name="value"/> has been serialized.</returns>
 972   public static async Task WriteBackendValueCheckNull(
 973      this PgSQLTypeFunctionality typeFunctionality,
 974      DataFormat dataFormat,
 975      PgSQLTypeDatabaseData boundData,
 976      BackendABIHelper helper,
 977      StreamWriterWithResizableBufferAndLimitedSize stream,
 978      Object value,
 979      BackendSizeInfo additionalInfoFromSize,
 980      Boolean isArrayElement
 981      )
 982   {
 120983      (var offset, var count) = stream.ReserveBufferSegment( sizeof( Int32 ) );
 126984      stream.Buffer.WritePgInt32( ref offset, value == null ? NULL_BYTE_COUNT : additionalInfoFromSize.ByteCount );
 123985      if ( additionalInfoFromSize.ByteCount > 0 )
 986      {
 115987         await typeFunctionality.WriteBackendValueAsync( dataFormat, boundData, helper, stream, value, additionalInfoFro
 988      }
 122989      await stream.FlushAsync();
 124990   }
 991}
 992

/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL/Types.cs

#LineLine coverage
 1/*
 2 * Copyright 2017 Stanislav Muhametsin. All rights Reserved.
 3 *
 4 * Licensed  under the  Apache License,  Version 2.0  (the "License");
 5 * you may not use  this file  except in  compliance with the License.
 6 * You may obtain a copy of the License at
 7 *
 8 *   http://www.apache.org/licenses/LICENSE-2.0
 9 *
 10 * Unless required by applicable law or agreed to in writing, software
 11 * distributed  under the  License is distributed on an "AS IS" BASIS,
 12 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
 13 * implied.
 14 *
 15 * See the License for the specific language governing permissions and
 16 * limitations under the License.
 17 */
 18using CBAM.Abstractions;
 19using CBAM.SQL.PostgreSQL;
 20using System;
 21using System.Collections.Generic;
 22using System.IO;
 23using System.Text;
 24using System.Threading;
 25using System.Threading.Tasks;
 26using UtilPack;
 27
 28// TODO document these later.
 29// TODO when (if?) we will have extension properties ( https://www.infoq.com/news/2017/08/CSharp-8 , under "extension ev
 30// Writing them now as extension methods would not be feasible in terms of user experience.
 31
 32#pragma warning disable 1591
 33namespace CBAM.SQL.PostgreSQL
 34{
 35   // TODO implement IPgTypeWithBackendTextFormat
 36   public struct PgSQLInterval : IComparable, IComparable<PgSQLInterval>, IEquatable<PgSQLInterval>
 37   {
 38      //// Getting decimal digits from System.Decimal: http://stackoverflow.com/questions/13477689/find-number-of-decima
 39      //private delegate Int32 GetDigitsDelegate( ref Decimal value );
 40
 41      //private static class DecimalHelper
 42      //{
 43      //   public static readonly GetDigitsDelegate GetDigits;
 44
 45      //   static DecimalHelper()
 46      //   {
 47      //      var value = Expression.Parameter( typeof( Decimal ).MakeByRefType(), "value" );
 48
 49      //      //return (value.flags & ~Int32.MinValue) >> 16
 50      //      var digits = Expression.RightShift(
 51      //          Expression.And( Expression.Field( value, "flags" ), Expression.Constant( ~Int32.MinValue, typeof( Int3
 52      //          Expression.Constant( 16, typeof( Int32 ) ) );
 53
 54      //      GetDigits = Expression.Lambda<GetDigitsDelegate>( digits, value ).Compile();
 55      //   }
 56      //}
 57
 58
 59      #region Consts
 60      private const Int32 DAYS_PER_MONTH = 30;
 61      private const Int32 MONTHS_PER_YEAR = 12;
 62      private const Int64 TICKS_PER_MONTH = TimeSpan.TicksPerDay * DAYS_PER_MONTH;
 63      internal const Int64 TICKS_PER_MICROSECOND = TimeSpan.TicksPerMillisecond / 1000;
 64      internal const Int64 MICROSECONDS_PER_SECOND = 1000000;
 65      internal const Int64 MILLISECONDS_PER_SECOND = 1000;
 66      internal const Int64 SECONDS_PER_MINUTE = 60;
 67      internal const Int64 MINUTES_PER_HOUR = 60;
 68
 69      #endregion
 70
 71      #region Static
 72
 73      public static PgSQLInterval MinValue = new PgSQLInterval( Int64.MinValue );
 74      public static PgSQLInterval MaxValue = new PgSQLInterval( Int64.MaxValue );
 75      public static PgSQLInterval Zero = new PgSQLInterval( 0 );
 76
 77      internal static void AppendTimeInformation( StringBuilder sb, Int64 ticks )
 78      {
 79         sb.Append( Math.Abs( CalcHours( ticks ) ).ToString( "D2" ) ) // Hours
 80            .Append( ':' )
 81            .Append( Math.Abs( CalcMinutes( ticks ) ).ToString( "D2" ) ) // Minutes
 82            .Append( ':' )
 83            // Calculate seconds part (total seconds minus whole minutes in seconds)
 84            .Append( Math.Abs( ticks / (Decimal) TimeSpan.TicksPerSecond - ( ticks / TimeSpan.TicksPerMinute ) * 60 ).To
 85      }
 86
 87
 88
 89      internal static Int32 CalcMicroseconds( Int64 ticks )
 90      {
 91         return (Int32) ( ( ticks / TICKS_PER_MICROSECOND ) % MICROSECONDS_PER_SECOND );
 92      }
 93
 94      internal static Int32 CalcMilliseconds( Int64 ticks )
 95      {
 96         return (Int32) ( ( ticks / TimeSpan.TicksPerMillisecond ) % MILLISECONDS_PER_SECOND );
 97      }
 98
 99      internal static Int32 CalcSeconds( Int64 ticks )
 100      {
 101         return (Int32) ( ( ticks / TimeSpan.TicksPerSecond ) % SECONDS_PER_MINUTE );
 102      }
 103
 104      internal static Int32 CalcMinutes( Int64 ticks )
 105      {
 106         return (Int32) ( ( ticks / TimeSpan.TicksPerMinute ) % MINUTES_PER_HOUR );
 107      }
 108
 109      internal static Int32 CalcHours( Int64 ticks )
 110      {
 111         return (Int32) ( ticks / TimeSpan.TicksPerHour );
 112      }
 113
 114      #endregion
 115
 116      #region Fields
 117      private readonly Int32 _months;
 118      private readonly Int32 _days;
 119      private readonly Int64 _ticks;
 120
 121      #endregion
 122
 123      #region Constructors
 124
 125      public PgSQLInterval( Int64 ticks )
 126         : this( 0, 0, ticks )
 127      {
 128
 129      }
 130
 131      public PgSQLInterval( TimeSpan span )
 132         : this( span.Ticks )
 133      {
 134
 135      }
 136
 137      public PgSQLInterval( Int32 months, Int32 days, Int64 ticks )
 138      {
 139         this._months = months;
 140         this._days = days;
 141         this._ticks = ticks;
 142      }
 143
 144      public PgSQLInterval( Int32 days, Int32 hours, Int32 minutes, Int32 seconds )
 145         : this( 0, days, new TimeSpan( hours, minutes, seconds ).Ticks )
 146      {
 147      }
 148
 149      public PgSQLInterval( Int32 days, Int32 hours, Int32 minutes, Int32 seconds, Int32 milliseconds )
 150         : this( 0, days, new TimeSpan( 0, hours, minutes, seconds, milliseconds ).Ticks )
 151      {
 152      }
 153
 154      public PgSQLInterval( Int32 months, Int32 days, Int32 hours, Int32 minutes, Int32 seconds, Int32 milliseconds )
 155         : this( months, days, new TimeSpan( 0, hours, minutes, seconds, milliseconds ).Ticks )
 156      {
 157      }
 158
 159      public PgSQLInterval( Int32 years, Int32 months, Int32 days, Int32 hours, Int32 minutes, Int32 seconds, Int32 mill
 160         : this( years * 12 + months, days, new TimeSpan( 0, hours, minutes, seconds, milliseconds ).Ticks )
 161      {
 162      }
 163
 164      #endregion
 165
 166      #region Whole parts
 167
 168      public Int64 Ticks
 169      {
 170         get
 171         {
 172            return this._ticks;
 173         }
 174      }
 175
 176      public Int32 Microseconds
 177      {
 178         get
 179         {
 180            return CalcMicroseconds( this._ticks );
 181         }
 182      }
 183
 184      public Int32 Milliseconds
 185      {
 186         get
 187         {
 188            return CalcMilliseconds( this._ticks );
 189         }
 190      }
 191
 192      public Int32 Seconds
 193      {
 194         get
 195         {
 196            return CalcSeconds( this._ticks );
 197         }
 198      }
 199
 200      public Int32 Minutes
 201      {
 202         get
 203         {
 204            return CalcMinutes( this._ticks );
 205         }
 206      }
 207
 208      public Int32 Hours
 209      {
 210         get
 211         {
 212            return CalcHours( this._ticks );
 213         }
 214      }
 215
 216      public Int32 Days
 217      {
 218         get
 219         {
 220            return this._days;
 221         }
 222      }
 223
 224      public Int32 Months
 225      {
 226         get
 227         {
 228            return this._months;
 229         }
 230      }
 231      #endregion
 232
 233      #region Total parts
 234
 235      public Int64 TotalTicks
 236      {
 237         get
 238         {
 239            return this._ticks + this._days * TimeSpan.TicksPerDay + this._months * TICKS_PER_MONTH;
 240         }
 241      }
 242
 243      public Double TotalMicroseconds
 244      {
 245         get
 246         {
 247            return this.TotalTicks / ( (Double) TICKS_PER_MICROSECOND );
 248         }
 249      }
 250
 251      public Double TotalMilliseconds
 252      {
 253         get
 254         {
 255            return this.TotalTicks / ( (Double) TimeSpan.TicksPerMillisecond );
 256         }
 257      }
 258
 259      public Double TotalSeconds
 260      {
 261         get
 262         {
 263            return this.TotalTicks / ( (Double) TimeSpan.TicksPerSecond );
 264         }
 265      }
 266
 267      public Double TotalMinutes
 268      {
 269         get
 270         {
 271            return this.TotalTicks / ( (Double) TimeSpan.TicksPerMinute );
 272         }
 273      }
 274
 275      public Double TotalHours
 276      {
 277         get
 278         {
 279            return this.TotalTicks / ( (Double) TimeSpan.TicksPerHour );
 280         }
 281      }
 282
 283      public Double TotalDays
 284      {
 285         get
 286         {
 287            return this.TotalTicks / ( (Double) TimeSpan.TicksPerDay );
 288         }
 289      }
 290
 291      public Double TotalMonths
 292      {
 293         get
 294         {
 295            return this.TotalTicks / ( (Double) TICKS_PER_MONTH );
 296         }
 297      }
 298
 299      #endregion
 300
 301      #region Justification
 302
 303      public PgSQLInterval JustifyDays()
 304      {
 305         return new PgSQLInterval( this._months, this._days + (Int32) ( this._ticks / TimeSpan.TicksPerDay ), this._tick
 306      }
 307
 308      public PgSQLInterval UnjustifyDays()
 309      {
 310         return new PgSQLInterval( this._months, 0, this._ticks + this._days * TimeSpan.TicksPerDay );
 311      }
 312
 313      public PgSQLInterval JustifyMonths()
 314      {
 315         return new PgSQLInterval( this._months + this._days / DAYS_PER_MONTH, this._days % DAYS_PER_MONTH, this._ticks 
 316      }
 317
 318      public PgSQLInterval UnjustifyMonths()
 319      {
 320         return new PgSQLInterval( 0, this._days + this._months * DAYS_PER_MONTH, this._ticks );
 321      }
 322
 323      public PgSQLInterval JustifyInterval()
 324      {
 325         return this.JustifyMonths().JustifyDays();
 326      }
 327
 328      public PgSQLInterval UnjustifyInterval()
 329      {
 330         return new PgSQLInterval( 0, 0, this._ticks + this._days * TimeSpan.TicksPerDay + this._months * TICKS_PER_MONT
 331      }
 332
 333      public PgSQLInterval Canonicalize()
 334      {
 335         return new PgSQLInterval( 0, this._days + this._months * DAYS_PER_MONTH + (Int32) ( this._ticks / TimeSpan.Tick
 336      }
 337
 338      #endregion
 339
 340      #region Arithmetic
 341
 342      public PgSQLInterval Add( PgSQLInterval another )
 343      {
 344         return new PgSQLInterval( this._months + another._months, this._days + another._days, this._ticks + another._ti
 345      }
 346
 347      public PgSQLInterval Subtract( PgSQLInterval another )
 348      {
 349         return new PgSQLInterval( this._months - another._months, this._days - another._days, this._ticks - another._ti
 350      }
 351
 352      public PgSQLInterval Negate()
 353      {
 354         return new PgSQLInterval( -this._months, -this._days, -this._ticks );
 355      }
 356
 357      public PgSQLInterval Duration()
 358      {
 359         return this.UnjustifyInterval().Ticks < 0 ? this.Negate() : this;
 360      }
 361
 362      #endregion
 363
 364      #region Comparison
 365
 366      public Int32 CompareTo( PgSQLInterval other )
 367      {
 368         return this.UnjustifyInterval().Ticks.CompareTo( other.UnjustifyInterval().Ticks );
 369      }
 370
 371      Int32 IComparable.CompareTo( Object obj )
 372      {
 373         if ( obj == null )
 374         {
 375            // This is always 'greater' than null
 376            return 1;
 377         }
 378         else if ( obj is PgSQLInterval )
 379         {
 380            return this.CompareTo( (PgSQLInterval) obj );
 381         }
 382         else
 383         {
 384            throw new ArgumentException( "Given object must be of type " + this.GetType() + " or null." );
 385         }
 386      }
 387
 388      public Boolean Equals( PgSQLInterval other )
 389      {
 390         return this._ticks == other._ticks && this._days == other._days && this._months == other._months;
 391      }
 392
 393      public override Boolean Equals( Object obj )
 394      {
 395         return obj != null && obj is PgSQLInterval && this.Equals( (PgSQLInterval) obj );
 396      }
 397
 398      public override Int32 GetHashCode()
 399      {
 400         return this.UnjustifyInterval().Ticks.GetHashCode();
 401      }
 402
 403      #endregion
 404
 405      #region Casts
 406
 407      public static implicit operator TimeSpan( PgSQLInterval x )
 408      {
 409         return new TimeSpan( x._ticks + x._days * TimeSpan.TicksPerDay + x._months * TICKS_PER_MONTH );
 410      }
 411
 412      public static implicit operator PgSQLInterval( TimeSpan x )
 413      {
 414         return new PgSQLInterval( x ).Canonicalize();
 415      }
 416
 417      #endregion
 418
 419      #region Creation from parts
 420
 421      public static PgSQLInterval FromTicks( Int64 ticks )
 422      {
 423         return new PgSQLInterval( ticks ).Canonicalize();
 424      }
 425
 426      public static PgSQLInterval FromMicroseconds( Double microseconds )
 427      {
 428         return FromTicks( (Int64) ( microseconds * ( TimeSpan.TicksPerMillisecond / 1000 ) ) );
 429      }
 430
 431      public static PgSQLInterval FromMilliseconds( Double milliseconds )
 432      {
 433         return FromTicks( (Int64) ( milliseconds * TimeSpan.TicksPerMillisecond ) );
 434      }
 435
 436      public static PgSQLInterval FromSeconds( Double seconds )
 437      {
 438         return FromTicks( (Int64) ( seconds * TimeSpan.TicksPerSecond ) );
 439      }
 440
 441      public static PgSQLInterval FromMinutes( Double minutes )
 442      {
 443         return FromTicks( (Int64) ( minutes * TimeSpan.TicksPerMinute ) );
 444      }
 445
 446      public static PgSQLInterval FromHours( Double hours )
 447      {
 448         return FromTicks( (Int64) ( hours * TimeSpan.TicksPerHour ) );
 449      }
 450
 451      public static PgSQLInterval FromDays( Double days )
 452      {
 453         return FromTicks( (Int64) ( days * TimeSpan.TicksPerDay ) );
 454      }
 455
 456      public static PgSQLInterval FromMonths( Double months )
 457      {
 458         return FromTicks( (Int64) ( months * TICKS_PER_MONTH ) );
 459      }
 460
 461      #endregion
 462
 463      #region Operators
 464
 465      public static PgSQLInterval operator +( PgSQLInterval x, PgSQLInterval y )
 466      {
 467         return x.Add( y );
 468      }
 469
 470      public static PgSQLInterval operator -( PgSQLInterval x, PgSQLInterval y )
 471      {
 472         return x.Subtract( y );
 473      }
 474
 475      public static Boolean operator ==( PgSQLInterval x, PgSQLInterval y )
 476      {
 477         return x.Equals( y );
 478      }
 479
 480      public static Boolean operator !=( PgSQLInterval x, PgSQLInterval y )
 481      {
 482         return !( x == y );
 483      }
 484
 485      public static Boolean operator <( PgSQLInterval x, PgSQLInterval y )
 486      {
 487         return x.UnjustifyInterval().Ticks < y.UnjustifyInterval().Ticks;
 488      }
 489
 490      public static Boolean operator <=( PgSQLInterval x, PgSQLInterval y )
 491      {
 492         return x.UnjustifyInterval().Ticks <= y.UnjustifyInterval().Ticks;
 493      }
 494
 495      public static Boolean operator >( PgSQLInterval x, PgSQLInterval y )
 496      {
 497         return !( x <= y );
 498      }
 499
 500      public static Boolean operator >=( PgSQLInterval x, PgSQLInterval y )
 501      {
 502         return !( x < y );
 503      }
 504
 505      public static PgSQLInterval operator +( PgSQLInterval x )
 506      {
 507         return x;
 508      }
 509
 510      public static PgSQLInterval operator -( PgSQLInterval x )
 511      {
 512         return x.Negate();
 513      }
 514
 515      #endregion
 516
 517      #region To and from string
 518
 519      public override String ToString()
 520      {
 521         var sb = new StringBuilder();
 522
 523         // Months
 524         if ( this._months != 0 )
 525         {
 526            sb.Append( this._months ).Append( Math.Abs( this._months ) == 1 ? " mon " : " mons " );
 527         }
 528
 529         // Days
 530         if ( this._days != 0 )
 531         {
 532            if ( this._months < 0 && this._days > 0 )
 533            {
 534               sb.Append( '+' );
 535            }
 536            sb.Append( this._days ).Append( Math.Abs( this._days ) == 1 ? " day " : " days " );
 537         }
 538
 539         // The rest
 540         if ( this._ticks != 0 || sb.Length == 0 )
 541         {
 542            // The sign
 543            if ( this._ticks < 0 )
 544            {
 545               sb.Append( '-' );
 546            }
 547            else if ( this._days < 0 || ( this._days == 0 && this._months < 0 ) )
 548            {
 549               sb.Append( '+' );
 550            }
 551            AppendTimeInformation( sb, this._ticks );
 552         }
 553
 554         return sb.ToString( 0, sb[sb.Length - 1] == ' ' ? ( sb.Length - 1 ) : sb.Length );
 555      }
 556
 557      public static PgSQLInterval Parse( String str )
 558      {
 559         PgSQLInterval result; Exception error;
 560         TryParse( str, out result, out error );
 561         if ( error != null )
 562         {
 563            throw error;
 564         }
 565         return result;
 566      }
 567
 568      public static Boolean TryParse( String str, out PgSQLInterval result )
 569      {
 570         Exception error;
 571         TryParse( str, out result, out error );
 572         return error == null;
 573      }
 574
 575      private static void TryParse( String str, out PgSQLInterval result, out Exception error )
 576      {
 577         if ( str == null )
 578         {
 579            result = default( PgSQLInterval );
 580            error = new ArgumentNullException( "String" );
 581         }
 582         else
 583         {
 584            // Easymode for plurals
 585            str = str.Replace( 's', ' ' );
 586            error = null;
 587
 588            // Initialize variables
 589            var years = 0;
 590            var months = 0;
 591            var days = 0;
 592            var ticks = 0L;
 593
 594            // Years
 595            var idx = str.IndexOf( "year" );
 596            var start = 0;
 597            if ( idx > 0 && !Int32.TryParse( str.Substring( start, idx - start ), out years ) )
 598            {
 599               error = new FormatException( "Years were in invalid format." );
 600            }
 601            UpdateStartIndex( str, idx, ref start, 5 );
 602
 603            // Months
 604            if ( error == null )
 605            {
 606               idx = str.IndexOf( "mon", start );
 607               if ( idx > 0 && !Int32.TryParse( str.Substring( start, idx - start ), out months ) )
 608               {
 609                  error = new FormatException( "Months were in invalid format." );
 610               }
 611               UpdateStartIndex( str, idx, ref start, 4 );
 612            }
 613
 614            // Days
 615            if ( error == null )
 616            {
 617               idx = str.IndexOf( "day", start );
 618               if ( idx > 0 && !Int32.TryParse( str.Substring( start, idx - start ), out days ) )
 619               {
 620                  error = new FormatException( "Days were in invalid format." );
 621               }
 622               UpdateStartIndex( str, idx, ref start, 4 );
 623            }
 624
 625            // Time
 626            if ( error == null )
 627            {
 628               Int32 hours, minutes; Decimal seconds; Boolean isNegative;
 629               ParseTime( str, start, out hours, out minutes, out seconds, out isNegative, ref error, false );
 630
 631               if ( error == null )
 632               {
 633                  try
 634                  {
 635                     ticks = hours * TimeSpan.TicksPerHour + minutes * TimeSpan.TicksPerMinute + (Int64) ( seconds * Tim
 636                  }
 637                  catch ( Exception exc )
 638                  {
 639                     // E.g. overflow exception
 640                     error = new FormatException( "Error when calculating ticks, ", exc );
 641                  }
 642               }
 643            }
 644
 645            result = error == null ?
 646               new PgSQLInterval( years * MONTHS_PER_YEAR + months, days, ticks ) :
 647               default( PgSQLInterval );
 648
 649         }
 650      }
 651
 652      private static void UpdateStartIndex( String str, Int32 idx, ref Int32 startIdx, Int32 addition )
 653      {
 654         if ( idx >= 0 )
 655         {
 656            startIdx = ( idx + addition ) >= str.Length ? str.Length : ( idx + addition );
 657         }
 658      }
 659
 660      internal static void ParseTime( String str, Int32 start, out Int32 hours, out Int32 minutes, out Decimal seconds, 
 661      {
 662         hours = 0;
 663         minutes = 0;
 664         seconds = 0m;
 665
 666         var seenOtherThanWhitespace = false;
 667         isNegative = false;
 668         var curState = 0; // 0 - hours, 1 - minutes, 2 - seconds
 669         var idx = start;
 670         var len = str.Length;
 671         while ( idx < len && error == null )
 672         {
 673            var c = str[idx];
 674            if ( !Char.IsWhiteSpace( c ) )
 675            {
 676               if ( c == '-' )
 677               {
 678                  if ( seenOtherThanWhitespace || isNegative )
 679                  {
 680                     error = new FormatException( "Unexpected minus sign." );
 681                  }
 682                  else
 683                  {
 684                     isNegative = true;
 685                  }
 686               }
 687               else if ( c == ':' || idx == len - 1 )
 688               {
 689                  var timeStr = str.Substring( start, idx - start + ( c == ':' ? 0 : 1 ) );
 690                  switch ( curState )
 691                  {
 692                     case 0: // Hours
 693                        if ( !Int32.TryParse( timeStr, out hours ) )
 694                        {
 695                           error = new FormatException( "Malformed hours." );
 696                        }
 697                        break;
 698                     case 1: // Minutes
 699                        if ( !Int32.TryParse( timeStr, out minutes ) )
 700                        {
 701                           error = new FormatException( "Malformed minutes." );
 702                        }
 703                        break;
 704                     case 2: // Seconds
 705                        if ( !Decimal.TryParse( timeStr, System.Globalization.NumberStyles.Number, System.Globalization.
 706                        {
 707                           error = new FormatException( "Malformed seconds." );
 708                        }
 709                        break;
 710                  }
 711
 712                  ++curState;
 713                  start = idx + 1;
 714               }
 715               seenOtherThanWhitespace = true;
 716
 717            }
 718            ++idx;
 719         }
 720
 721         if ( curState == 0 && timeIsMandatory )
 722         {
 723            error = new FormatException( "Missing time information." );
 724         }
 725
 726         if ( isNegative )
 727         {
 728            minutes = -minutes;
 729            seconds = -seconds;
 730         }
 731      }
 732
 733      #endregion
 734
 735   }
 736
 737   //public interface IPgSQLDate
 738   //{
 739   //   #region Properties
 740
 741   //   Int32 DayOfYear { get; }
 742
 743   //   Int32 Year { get; }
 744
 745   //   Int32 Month { get; }
 746
 747   //   Int32 Day { get; }
 748
 749   //   DayOfWeek DayOfWeek { get; }
 750
 751   //   Int32 DaysSinceEra { get; }
 752
 753   //   Boolean IsLeapYear { get; }
 754
 755   //   #endregion
 756   //}
 757
 758   public struct PgSQLDate : IEquatable<PgSQLDate>, IComparable, IComparable<PgSQLDate>//, IPgSQLDate
 759   {
 760      #region Consts
 761      public const Int32 MAX_YEAR = 5874897; // As per PostgreSQL documentation
 762      public const Int32 MIN_YEAR = -4714; // As per PostgreSQL documentation
 763
 764      private const Int32 DAYS_IN_YEAR = 365; //Common years
 765      private const Int32 DAYS_IN_4YEARS = 4 * DAYS_IN_YEAR + 1; //Leap year every 4 years.
 766      private const Int32 DAYS_IN_CENTURY = 25 * DAYS_IN_4YEARS - 1; //Except no leap year every 100.
 767      private const Int32 DAYS_IN_4CENTURIES = 4 * DAYS_IN_CENTURY + 1; //Except leap year every 400.
 768
 769      internal const String INFINITY = "infinity";
 770      internal const String MINUS_INFINITY = "-" + INFINITY;
 771
 772
 773
 774      #endregion
 775
 776      #region Static
 777
 778      // Cumulative days in non-leap years
 779      private static readonly Int32[] CommonYearDays = new Int32[] { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 3
 780
 781      // Cumulative days in leap years
 782      private static readonly Int32[] LeapYearDays = new Int32[] { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335
 783
 784      // Amount of days in non-leap year months
 785      private static readonly Int32[] CommonYearMaxes = new Int32[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
 786
 787      // Amount of days in leap year months
 788      private static readonly Int32[] LeapYearMaxes = new Int32[] { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
 789
 790      private static Boolean IsLeap( Int32 year )
 791      {
 792         //Every 4 years is a leap year
 793         //Except every 100 years isn't a leap year.
 794         //Except every 400 years is.
 795         // Also: http://support.microsoft.com/kb/214019 (doesn't cover 0 and negative years)
 796         if ( year < 1 )
 797         {
 798            ++year;
 799         }
 800         return ( year % 4 == 0 ) && ( ( year % 100 != 0 ) || ( year % 400 == 0 ) );
 801      }
 802
 803      private static Int32 DaysForYears( Int32 years )
 804      {
 805         //Number of years after 1CE (0 for 1CE, -1 for 1BCE, 1 for 2CE).
 806         if ( years >= 1 )
 807         {
 808            --years;
 809         }
 810
 811         return years / 400 * DAYS_IN_4CENTURIES //Blocks of 400 years with their leap and common years
 812                + years % 400 / 100 * DAYS_IN_CENTURY //Remaining blocks of 100 years with their leap and common years
 813                + years % 100 / 4 * DAYS_IN_4YEARS //Remaining blocks of 4 years with their leap and common years
 814                + years % 4 * DAYS_IN_YEAR //Remaining years, all common
 815                + ( years < 0 ? -1 : 0 ); //And 1BCE is leap.
 816      }
 817
 818      private static Int32 ComponentsToDays( Int32 year, Int32 month, Int32 day )
 819      {
 820         if ( year == 0 || year < MIN_YEAR || year > MAX_YEAR )
 821         {
 822            throw new ArgumentOutOfRangeException( "Year" );
 823         }
 824         else if ( month < 1 || month > 12 )
 825         {
 826            throw new ArgumentOutOfRangeException( "Month" );
 827         }
 828         else
 829         {
 830            var isLeap = IsLeap( year );
 831            if ( day < 1 || day > ( isLeap ? 366 : 365 ) )
 832            {
 833               throw new ArgumentOutOfRangeException( "Day" );
 834            }
 835            else
 836            {
 837               return DaysForYears( year ) + ( isLeap ? LeapYearDays : CommonYearDays )[month - 1] + day - 1;
 838            }
 839         }
 840      }
 841
 842      public static readonly PgSQLDate Epoch = new PgSQLDate( 1970, 1, 1 );
 843      public static readonly PgSQLDate MaxValue = new PgSQLDate( MAX_YEAR, 12, 31 );
 844      public static readonly PgSQLDate MinValue = new PgSQLDate( MIN_YEAR, 11, 24 );
 845      public static readonly PgSQLDate Era = new PgSQLDate( 0 );
 846      public static readonly PgSQLDate Infinity = new PgSQLDate( DateTime.MaxValue );
 847      public static readonly PgSQLDate MinusInfinity = new PgSQLDate( DateTime.MinValue );
 848
 849      public static PgSQLDate Now
 850      {
 851         get
 852         {
 853            return new PgSQLDate( DateTime.Now );
 854         }
 855      }
 856
 857      public static PgSQLDate Today
 858      {
 859         get
 860         {
 861            return Now;
 862         }
 863      }
 864
 865      public static PgSQLDate Yesterday
 866      {
 867         get
 868         {
 869            return Now.AddDays( -1 );
 870         }
 871      }
 872
 873      public static PgSQLDate Tomorrow
 874      {
 875         get
 876         {
 877            return Now.AddDays( 1 );
 878         }
 879      }
 880
 881      #endregion
 882
 883      #region Fields
 884
 885      private readonly Int32 _days;
 886
 887      #endregion
 888
 889      #region Constructors
 890
 891      public PgSQLDate( Int32 daysSinceEra )
 892      {
 893         this._days = daysSinceEra;
 894      }
 895
 896      public PgSQLDate( PgSQLDate other )
 897         : this( other._days )
 898      {
 899      }
 900
 901      public PgSQLDate( DateTime datetime )
 902         : this( (Int32) ( datetime.Ticks / TimeSpan.TicksPerDay ) )
 903      {
 904      }
 905
 906      public PgSQLDate( Int32 year, Int32 month, Int32 day )
 907         : this( ComponentsToDays( year, month, day ) )
 908      {
 909      }
 910
 911      #endregion
 912
 913      #region Properties
 914
 915      public Int32 DayOfYear
 916      {
 917         get
 918         {
 919            return this._days - DaysForYears( this.Year ) + 1;
 920         }
 921      }
 922
 923      public Int32 Year
 924      {
 925         get
 926         {
 927            var start = ( (Int32) Math.Round( this._days / 365.2425 ) ) - 1;
 928            while ( DaysForYears( ++start ) <= this._days ) ;
 929            return start - 1;
 930         }
 931      }
 932
 933      public Int32 Month
 934      {
 935         get
 936         {
 937            var max = this.DayOfYear;
 938            var array = this.IsLeapYear ? LeapYearDays : CommonYearDays;
 939            var i = 1;
 940            while ( max > array[i] )
 941            {
 942               ++i;
 943            }
 944            return i;
 945         }
 946      }
 947
 948      public Int32 Day
 949      {
 950         get
 951         {
 952            return this.DayOfYear - ( this.IsLeapYear ? LeapYearDays : CommonYearDays )[this.Month - 1];
 953         }
 954      }
 955
 956      public DayOfWeek DayOfWeek
 957      {
 958         get
 959         {
 960            return (DayOfWeek) ( ( this._days + 1 ) % 7 );
 961         }
 962      }
 963
 964      public Int32 DaysSinceEra
 965      {
 966         get
 967         {
 968            return this._days;
 969         }
 970      }
 971
 972      public Boolean IsLeapYear
 973      {
 974         get
 975         {
 976            return IsLeap( this.Year );
 977         }
 978      }
 979
 980      #endregion
 981
 982      #region Arithmetics
 983
 984      public PgSQLDate AddDays( Int32 days )
 985      {
 986         return new PgSQLDate( this._days + days );
 987      }
 988
 989      public PgSQLDate AddMonths( Int32 months )
 990      {
 991         var newYear = this.Year;
 992         var newMonth = this.Month + months;
 993
 994         while ( newMonth > 12 )
 995         {
 996            newMonth -= 12;
 997            ++newYear;
 998            if ( newYear == 0 )
 999            {
 1000               ++newYear; // No 'zero'eth year.
 1001            }
 1002         };
 1003         while ( newMonth < 1 )
 1004         {
 1005            newMonth += 12;
 1006            --newYear;
 1007            if ( newYear == 0 )
 1008            {
 1009               --newYear; // No 'zero'eth year.
 1010            }
 1011         };
 1012         var maxDay = ( IsLeap( newYear ) ? LeapYearMaxes : CommonYearMaxes )[newMonth - 1];
 1013         var newDay = this.Day > maxDay ? maxDay : this.Day;
 1014         return new PgSQLDate( newYear, newMonth, newDay );
 1015      }
 1016
 1017      public PgSQLDate AddYears( Int32 years )
 1018      {
 1019         var newYear = this.Year + years;
 1020         if ( newYear >= 0 && this._days < 0 ) // No 'zero'eth year.
 1021         {
 1022            ++newYear;
 1023         }
 1024         else if ( newYear <= 0 && this._days >= 0 ) // No 'zero'eth year.
 1025         {
 1026            --newYear;
 1027         }
 1028         return new PgSQLDate( newYear, Month, Day );
 1029      }
 1030
 1031      public PgSQLDate Add( PgSQLInterval interval, Int32 carriedOverflow = 0 )
 1032      {
 1033         return this.AddMonths( interval.Months ).AddDays( interval.Days + carriedOverflow );
 1034      }
 1035
 1036      #endregion
 1037
 1038      #region Comparison
 1039
 1040      public Boolean Equals( PgSQLDate other )
 1041      {
 1042         return this._days == other._days;
 1043      }
 1044
 1045      public override Boolean Equals( Object obj )
 1046      {
 1047         return obj != null && obj is PgSQLDate && this.Equals( (PgSQLDate) obj );
 1048      }
 1049
 1050      public override Int32 GetHashCode()
 1051      {
 1052         return this._days.GetHashCode();
 1053      }
 1054
 1055      Int32 IComparable.CompareTo( Object obj )
 1056      {
 1057         if ( obj == null )
 1058         {
 1059            // This is always 'greater' than null
 1060            return 1;
 1061         }
 1062         else if ( obj is PgSQLDate )
 1063         {
 1064            return this.CompareTo( (PgSQLDate) obj );
 1065         }
 1066         else
 1067         {
 1068            throw new ArgumentException( "Given object must be of type " + this.GetType() + " or null." );
 1069         }
 1070      }
 1071
 1072      public Int32 CompareTo( PgSQLDate other )
 1073      {
 1074         return this._days.CompareTo( other._days );
 1075      }
 1076
 1077      #endregion
 1078
 1079      #region Casts
 1080
 1081      public static explicit operator DateTime( PgSQLDate x )
 1082      {
 1083         try
 1084         {
 1085            return new DateTime( x._days * TimeSpan.TicksPerDay );
 1086         }
 1087         catch
 1088         {
 1089            throw new InvalidCastException( "The given PostgreSQL date " + x + " can not be represented by " + typeof( D
 1090         }
 1091      }
 1092
 1093      public static explicit operator PgSQLDate( DateTime x )
 1094      {
 1095         return new PgSQLDate( (Int32) ( x.Ticks / TimeSpan.TicksPerDay ) );
 1096      }
 1097
 1098      #endregion
 1099
 1100      #region Operators
 1101
 1102      public static Boolean operator ==( PgSQLDate x, PgSQLDate y )
 1103      {
 1104         return x.Equals( y );
 1105      }
 1106
 1107      public static Boolean operator !=( PgSQLDate x, PgSQLDate y )
 1108      {
 1109         return !( x == y );
 1110      }
 1111
 1112      public static Boolean operator <( PgSQLDate x, PgSQLDate y )
 1113      {
 1114         return x._days < y._days;
 1115      }
 1116
 1117      public static Boolean operator >( PgSQLDate x, PgSQLDate y )
 1118      {
 1119         return !( x._days <= y._days );
 1120      }
 1121
 1122      public static Boolean operator <=( PgSQLDate x, PgSQLDate y )
 1123      {
 1124         return x._days <= y._days;
 1125      }
 1126
 1127      public static Boolean operator >=( PgSQLDate x, PgSQLDate y )
 1128      {
 1129         return !( x._days > y._days );
 1130      }
 1131
 1132      public static PgSQLDate operator +( PgSQLDate date, PgSQLInterval interval )
 1133      {
 1134         return date.Add( interval );
 1135      }
 1136
 1137      public static PgSQLDate operator +( PgSQLInterval interval, PgSQLDate date )
 1138      {
 1139         return date.Add( interval );
 1140      }
 1141
 1142      public static PgSQLDate operator -( PgSQLDate date, PgSQLInterval interval )
 1143      {
 1144         return date.Add( -interval );
 1145      }
 1146
 1147      public static PgSQLInterval operator -( PgSQLDate dateX, PgSQLDate dateY )
 1148      {
 1149         return new PgSQLInterval( 0, dateX._days - dateY._days, 0 );
 1150      }
 1151
 1152      #endregion
 1153
 1154      #region To and from string
 1155
 1156      internal const Int32 INFINITY_CHAR_COUNT = 8;
 1157      internal const Int32 MINUS_INFINITY_CHAR_COUNT = INFINITY_CHAR_COUNT + 1;
 1158      private const Int32 MIN_NORMAL_CHAR_COUNT = YEAR_CHAR_COUNT + 1 + MONTH_CHAR_COUNT + 1 + DAY_CHAR_COUNT;
 1159      private const Byte SEPARATOR = (Byte) '-';
 1160      private const Int32 YEAR_CHAR_COUNT = 4;
 1161      private const Int32 MONTH_CHAR_COUNT = 2;
 1162      private const Int32 DAY_CHAR_COUNT = 2;
 1163      private const Int32 BC_CHAR_COUNT = 3; // " BC"
 1164      private const Byte BC_CHAR_1 = (Byte) ' ';
 1165      private const Byte BC_CHAR_2 = (Byte) 'B';
 1166      private const Byte BC_CHAR_3 = (Byte) 'C';
 1167
 1168      public Int32 GetTextByteCount( IEncodingInfo encoding )
 1169      {
 1170         Int32 retVal;
 1171         if ( this == Infinity )
 1172         {
 1173            retVal = INFINITY_CHAR_COUNT * encoding.BytesPerASCIICharacter;
 1174         }
 1175         else if ( this == MinusInfinity )
 1176         {
 1177            retVal = INFINITY_CHAR_COUNT * encoding.BytesPerASCIICharacter;
 1178         }
 1179         else
 1180         {
 1181            // yyyy-MM-dd
 1182            retVal = MIN_NORMAL_CHAR_COUNT * encoding.BytesPerASCIICharacter;
 1183            if ( this._days < 0 )
 1184            {
 1185               // " BC"
 1186               retVal += BC_CHAR_COUNT * encoding.BytesPerASCIICharacter;
 1187            }
 1188         }
 1189         return retVal;
 1190      }
 1191
 1192      public void WriteTextBytes( IEncodingInfo encoding, Byte[] array, ref Int32 offset )
 1193      {
 1194         if ( this == Infinity )
 1195         {
 1196            encoding.WriteString( array, ref offset, INFINITY );
 1197         }
 1198         else if ( this == MinusInfinity )
 1199         {
 1200            encoding.WriteString( array, ref offset, MINUS_INFINITY );
 1201         }
 1202         else
 1203         {
 1204            // Let's not allocate heap objects
 1205            encoding
 1206               .WriteIntegerTextual( array, ref offset, Math.Abs( this.Year ), YEAR_CHAR_COUNT )
 1207               .WriteASCIIByte( array, ref offset, SEPARATOR ) // '-'
 1208               .WriteIntegerTextual( array, ref offset, this.Month, MONTH_CHAR_COUNT )
 1209               .WriteASCIIByte( array, ref offset, SEPARATOR ) // '-'
 1210               .WriteIntegerTextual( array, ref offset, this.Day, DAY_CHAR_COUNT );
 1211            if ( this._days < 0 )
 1212            {
 1213               // " BC"
 1214               encoding
 1215                  .WriteASCIIByte( array, ref offset, BC_CHAR_1 ) // ' '
 1216                  .WriteASCIIByte( array, ref offset, BC_CHAR_2 ) // 'B'
 1217                  .WriteASCIIByte( array, ref offset, BC_CHAR_3 ); // 'C'
 1218            }
 1219         }
 1220      }
 1221
 1222      public static PgSQLDate ParseBinaryText( IEncodingInfo encoding, Byte[] array, ref Int32 offset, Int32 count )
 1223      {
 1224         var increment = encoding.BytesPerASCIICharacter;
 1225         switch ( increment * count )
 1226         {
 1227            case INFINITY_CHAR_COUNT:
 1228               return Infinity;
 1229            case MINUS_INFINITY_CHAR_COUNT:
 1230               return MinusInfinity;
 1231            default:
 1232               var max = offset + count;
 1233               var year = encoding.ParseInt32Textual( array, ref offset, (YEAR_CHAR_COUNT, true) );
 1234               var month = encoding.EqualsOrThrow( array, ref offset, SEPARATOR ).ParseInt32Textual( array, ref offset, 
 1235               var day = encoding.EqualsOrThrow( array, ref offset, SEPARATOR ).ParseInt32Textual( array, ref offset, (D
 1236               if ( offset + 3 * encoding.BytesPerASCIICharacter < max )
 1237               {
 1238                  // " BC" may follow
 1239                  max = offset;
 1240                  if ( encoding.ReadASCIIByte( array, ref offset ) == BC_CHAR_1
 1241                     && encoding.ReadASCIIByte( array, ref offset ) == BC_CHAR_2
 1242                     && encoding.ReadASCIIByte( array, ref offset ) == BC_CHAR_3
 1243                     )
 1244                  {
 1245                     year = -year;
 1246                  }
 1247                  else
 1248                  {
 1249                     // 'Reverse back'
 1250                     offset = max;
 1251                  }
 1252               }
 1253               return new PgSQLDate( year, month, day );
 1254         }
 1255      }
 1256
 1257      public override String ToString()
 1258      {
 1259         // As per PostgreSQL documentation ISO 8601 format (same as in Npgsql)
 1260         // Format of yyyy-MM-dd with " BC" for BCE and optional " AD" for CE which we omit here.
 1261         return
 1262             new StringBuilder( Math.Abs( this.Year ).ToString( "D4" ) ).Append( '-' ).Append( this.Month.ToString( "D2"
 1263                 this.Day.ToString( "D2" ) ).Append( this._days < 0 ? " BC" : "" ).ToString();
 1264      }
 1265
 1266      public static PgSQLDate Parse( String str )
 1267      {
 1268         PgSQLDate result; Exception error;
 1269         TryParse( str, out result, out error );
 1270         if ( error != null )
 1271         {
 1272            throw error;
 1273         }
 1274         return result;
 1275      }
 1276
 1277      public static Boolean TryParse( String str, out PgSQLDate result )
 1278      {
 1279         Exception error;
 1280         return TryParse( str, out result, out error );
 1281      }
 1282
 1283      internal static Boolean TryParse( String str, out PgSQLDate result, out Exception error )
 1284      {
 1285         if ( str == null )
 1286         {
 1287            result = default( PgSQLDate );
 1288            error = new ArgumentNullException( "String" );
 1289         }
 1290         else
 1291         {
 1292            str = str.Trim();
 1293            error = null;
 1294            if ( String.Equals( str, INFINITY, StringComparison.OrdinalIgnoreCase ) )
 1295            {
 1296               result = Infinity;
 1297            }
 1298            else if ( String.Equals( str, MINUS_INFINITY, StringComparison.OrdinalIgnoreCase ) )
 1299            {
 1300               result = MinusInfinity;
 1301            }
 1302            else
 1303            {
 1304               // ISO 8601 format assumed
 1305               var start = 0;
 1306               var idx = 0;
 1307               var year = IntegerOrError( str, ref start, ref idx, ref error, "year", "month", '-', true );
 1308               var month = IntegerOrError( str, ref start, ref idx, ref error, "month", "day", '-', true );
 1309               var day = IntegerOrError( str, ref start, ref idx, ref error, "day", null, ' ', false );
 1310               if ( error == null && start < str.Length && str.IndexOf( "BC", start, StringComparison.OrdinalIgnoreCase 
 1311               {
 1312                  year = -year;
 1313               }
 1314
 1315               result = error == null ?
 1316                  new PgSQLDate( year, month, day ) :
 1317                  default( PgSQLDate );
 1318            }
 1319         }
 1320         return error == null;
 1321      }
 1322
 1323      private static Int32 IntegerOrError( String str, ref Int32 start, ref Int32 idx, ref Exception error, String thisD
 1324      {
 1325         var result = 0;
 1326         if ( error == null )
 1327         {
 1328            if ( start >= str.Length )
 1329            {
 1330               error = new FormatException( "Missing " + thisDatePart );
 1331            }
 1332            else
 1333            {
 1334               idx = str.IndexOf( separator, start );
 1335               if ( idx == -1 && !separatorIsMandatory )
 1336               {
 1337                  idx = str.Length;
 1338               }
 1339
 1340               if ( idx == -1 )
 1341               {
 1342                  error = new FormatException( "Could not find " + thisDatePart + "-" + nextDatePart + " separator." );
 1343               }
 1344               else if ( !Int32.TryParse( str.Substring( start, idx - start ), out result ) )
 1345               {
 1346                  error = new FormatException( thisDatePart + " was malformed." );
 1347               }
 1348               else
 1349               {
 1350                  start = idx + 1;
 1351               }
 1352            }
 1353         }
 1354         return result;
 1355      }
 1356
 1357      #endregion
 1358
 1359   }
 1360
 1361   //public interface IPgSQLTime
 1362   //{
 1363   //   #region Properties
 1364
 1365   //   Int64 Ticks { get; }
 1366
 1367   //   Int32 Microseconds { get; }
 1368
 1369   //   Int32 Milliseconds { get; }
 1370
 1371   //   Int32 Seconds { get; }
 1372
 1373   //   Int32 Minutes { get; }
 1374
 1375   //   Int32 Hours { get; }
 1376
 1377   //   #endregion
 1378   //}
 1379
 1380   public struct PgSQLTime : IEquatable<PgSQLTime>, IComparable, IComparable<PgSQLTime>//, IPgSQLTime
 1381   {
 1382      #region Static
 1383
 1384      public static readonly PgSQLTime AllBalls = new PgSQLTime( 0 );
 1385
 1386      public static PgSQLTime Now
 1387      {
 1388         get
 1389         {
 1390            return new PgSQLTime( DateTime.Now.TimeOfDay );
 1391         }
 1392      }
 1393
 1394      #endregion
 1395
 1396      #region Fields
 1397      private readonly Int64 _ticks;
 1398      #endregion
 1399
 1400      #region Constructors
 1401
 1402      public PgSQLTime( Int64 ticks )
 1403      {
 1404         if ( ticks == TimeSpan.TicksPerDay )
 1405         {
 1406            this._ticks = ticks;
 1407         }
 1408         else
 1409         {
 1410            ticks %= TimeSpan.TicksPerDay;
 1411            this._ticks = ticks < 0 ? ticks + TimeSpan.TicksPerDay : ticks;
 1412         }
 1413      }
 1414
 1415      public PgSQLTime( TimeSpan timeSpan )
 1416         : this( timeSpan.Ticks )
 1417      {
 1418      }
 1419
 1420      public PgSQLTime( DateTime dateTime )
 1421         : this( dateTime.Ticks )
 1422      {
 1423      }
 1424
 1425      public PgSQLTime( PgSQLInterval interval )
 1426         : this( interval.Ticks )
 1427      {
 1428      }
 1429
 1430      public PgSQLTime( PgSQLTime other )
 1431         : this( other._ticks )
 1432      {
 1433      }
 1434
 1435      public PgSQLTime( Int32 hours, Int32 minutes, Int32 seconds )
 1436         : this( hours, minutes, seconds, 0 )
 1437      {
 1438      }
 1439
 1440      public PgSQLTime( Int32 hours, Int32 minutes, Int32 seconds, Int32 microseconds )
 1441         : this(
 1442             hours * TimeSpan.TicksPerHour + minutes * TimeSpan.TicksPerMinute + seconds * TimeSpan.TicksPerSecond +
 1443             microseconds * PgSQLInterval.TICKS_PER_MICROSECOND )
 1444      {
 1445      }
 1446
 1447      public PgSQLTime( Int32 hours, Int32 minutes, Decimal seconds )
 1448         : this( hours * TimeSpan.TicksPerHour + minutes * TimeSpan.TicksPerMinute + (Int64) ( seconds * TimeSpan.TicksP
 1449      {
 1450      }
 1451
 1452      #endregion
 1453
 1454      #region Properties
 1455
 1456      public Int64 Ticks
 1457      {
 1458         get
 1459         {
 1460            return this._ticks;
 1461         }
 1462      }
 1463
 1464      public Int32 Microseconds
 1465      {
 1466         get
 1467         {
 1468            return PgSQLInterval.CalcMicroseconds( this._ticks );
 1469         }
 1470      }
 1471
 1472      public Int32 Milliseconds
 1473      {
 1474         get
 1475         {
 1476            return PgSQLInterval.CalcMilliseconds( this._ticks );
 1477         }
 1478      }
 1479
 1480      public Int32 Seconds
 1481      {
 1482         get
 1483         {
 1484            return PgSQLInterval.CalcSeconds( this._ticks );
 1485         }
 1486      }
 1487
 1488      public Int32 Minutes
 1489      {
 1490         get
 1491         {
 1492            return PgSQLInterval.CalcMinutes( this._ticks );
 1493         }
 1494      }
 1495
 1496      public Int32 Hours
 1497      {
 1498         get
 1499         {
 1500            return PgSQLInterval.CalcHours( this._ticks );
 1501         }
 1502      }
 1503
 1504      #endregion
 1505
 1506      #region Comparison
 1507
 1508      public Boolean Equals( PgSQLTime other )
 1509      {
 1510         return this._ticks == other._ticks;
 1511      }
 1512
 1513      public override Boolean Equals( Object obj )
 1514      {
 1515         return obj != null && obj is PgSQLTime && this.Equals( (PgSQLTime) obj );
 1516      }
 1517
 1518      public override Int32 GetHashCode()
 1519      {
 1520         return this._ticks.GetHashCode();
 1521      }
 1522
 1523      Int32 IComparable.CompareTo( Object obj )
 1524      {
 1525         if ( obj == null )
 1526         {
 1527            // This is always 'greater' than null
 1528            return 1;
 1529         }
 1530         else if ( obj is PgSQLTime )
 1531         {
 1532            return this.CompareTo( (PgSQLTime) obj );
 1533         }
 1534         else
 1535         {
 1536            throw new ArgumentException( "Given object must be of type " + this.GetType() + " or null." );
 1537         }
 1538      }
 1539
 1540      public Int32 CompareTo( PgSQLTime other )
 1541      {
 1542         return this.Normalize()._ticks.CompareTo( other.Normalize()._ticks );
 1543      }
 1544
 1545      #endregion
 1546
 1547      #region Normalization
 1548
 1549      public PgSQLTime Normalize()
 1550      {
 1551         return new PgSQLTime( this._ticks % TimeSpan.TicksPerDay );
 1552      }
 1553
 1554      #endregion
 1555
 1556      #region Arithmetics
 1557
 1558      public PgSQLTime AddTicks( Int64 ticksAdded )
 1559      {
 1560         return new PgSQLTime( ( Ticks + ticksAdded ) % TimeSpan.TicksPerDay );
 1561      }
 1562
 1563      private PgSQLTime AddTicks( Int64 ticksAdded, out Int32 overflow )
 1564      {
 1565         var result = Ticks + ticksAdded;
 1566         overflow = (Int32) ( result / TimeSpan.TicksPerDay );
 1567         result %= TimeSpan.TicksPerDay;
 1568         if ( result < 0 )
 1569         {
 1570            --overflow; //"carry the one"
 1571         }
 1572         return new PgSQLTime( result );
 1573      }
 1574
 1575      public PgSQLTime Add( PgSQLInterval interval )
 1576      {
 1577         return AddTicks( interval.Ticks );
 1578      }
 1579
 1580      internal PgSQLTime Add( PgSQLInterval interval, out Int32 overflow )
 1581      {
 1582         return AddTicks( interval.Ticks, out overflow );
 1583      }
 1584
 1585      public PgSQLTime Subtract( PgSQLInterval interval )
 1586      {
 1587         return AddTicks( -interval.Ticks );
 1588      }
 1589
 1590      public PgSQLInterval Subtract( PgSQLTime earlier )
 1591      {
 1592         return new PgSQLInterval( Ticks - earlier.Ticks );
 1593      }
 1594
 1595      #endregion
 1596
 1597      #region Timezones
 1598
 1599      public PgSQLTimeTZ AtTimeZone( PgSQLTimeZone timeZone )
 1600      {
 1601         return new PgSQLTimeTZ( this ).AtTimeZone( timeZone );
 1602      }
 1603
 1604      #endregion
 1605
 1606      #region Casts
 1607
 1608      public static explicit operator DateTime( PgSQLTime x )
 1609      {
 1610         return new DateTime( x._ticks, DateTimeKind.Unspecified );
 1611      }
 1612
 1613      public static explicit operator PgSQLTime( DateTime x )
 1614      {
 1615         return new PgSQLTime( x.Ticks );
 1616      }
 1617
 1618      public static explicit operator TimeSpan( PgSQLTime x )
 1619      {
 1620         return new TimeSpan( x._ticks );
 1621      }
 1622
 1623      public static explicit operator PgSQLTime( TimeSpan x )
 1624      {
 1625         return new PgSQLTime( x.Ticks );
 1626      }
 1627
 1628      public static explicit operator PgSQLInterval( PgSQLTime x )
 1629      {
 1630         return new PgSQLInterval( x._ticks );
 1631      }
 1632
 1633      public static explicit operator PgSQLTime( PgSQLInterval x )
 1634      {
 1635         return new PgSQLTime( x );
 1636      }
 1637
 1638      #endregion
 1639
 1640      #region Operators
 1641
 1642      public static Boolean operator ==( PgSQLTime x, PgSQLTime y )
 1643      {
 1644         return x.Equals( y );
 1645      }
 1646
 1647      public static Boolean operator !=( PgSQLTime x, PgSQLTime y )
 1648      {
 1649         return !( x == y );
 1650      }
 1651
 1652      public static Boolean operator <( PgSQLTime x, PgSQLTime y )
 1653      {
 1654         return x.Ticks < y.Ticks;
 1655      }
 1656
 1657      public static Boolean operator <=( PgSQLTime x, PgSQLTime y )
 1658      {
 1659         return x.Ticks <= y.Ticks;
 1660      }
 1661
 1662      public static Boolean operator >( PgSQLTime x, PgSQLTime y )
 1663      {
 1664         return !( x.Ticks <= y.Ticks );
 1665      }
 1666
 1667      public static Boolean operator >=( PgSQLTime x, PgSQLTime y )
 1668      {
 1669         return !( x.Ticks < y.Ticks );
 1670      }
 1671
 1672      public static PgSQLTime operator +( PgSQLTime time, PgSQLInterval interval )
 1673      {
 1674         return time.Add( interval );
 1675      }
 1676
 1677      public static PgSQLTime operator +( PgSQLInterval interval, PgSQLTime time )
 1678      {
 1679         return time + interval;
 1680      }
 1681
 1682      public static PgSQLTime operator -( PgSQLTime time, PgSQLInterval interval )
 1683      {
 1684         return time.Subtract( interval );
 1685      }
 1686
 1687      public static PgSQLInterval operator -( PgSQLTime later, PgSQLTime earlier )
 1688      {
 1689         return later.Subtract( earlier );
 1690      }
 1691
 1692      #endregion
 1693
 1694      #region To and from string
 1695
 1696      private const Byte SEPARATOR = (Byte) ':';
 1697      private const Byte MICRO_SEPARATOR = (Byte) '.';
 1698      private const Int32 HOUR_CHAR_COUNT = 2;
 1699      private const Int32 MINUTE_CHAR_COUNT = 2;
 1700      private const Int32 SECOND_CHAR_COUNT = 2;
 1701      private const Int32 MICRO_PRECISION = 6;
 1702
 1703      internal static Int32 GetTextByteCount( IEncodingInfo encoding, Int64 ticks )
 1704      {
 1705         var retVal = 8 * encoding.BytesPerASCIICharacter; // HH:mm:ss
 1706         var microSeconds = Math.Abs( PgSQLInterval.CalcMicroseconds( ticks ) );
 1707         if ( microSeconds > 0 )
 1708         {
 1709            // Need to append '.' and micro second count
 1710            retVal += encoding.BytesPerASCIICharacter + encoding.GetTextualFractionIntegerSize( microSeconds, MICRO_PREC
 1711         }
 1712
 1713         return retVal;
 1714      }
 1715
 1716      internal static void WriteTextBytes( IEncodingInfo encoding, Int64 ticks, Byte[] array, ref Int32 offset )
 1717      {
 1718         encoding
 1719            .WriteIntegerTextual( array, ref offset, Math.Abs( (Int32) ( ticks / TimeSpan.TicksPerHour ) ), HOUR_CHAR_CO
 1720            .WriteASCIIByte( array, ref offset, SEPARATOR ) // ':'
 1721            .WriteIntegerTextual( array, ref offset, Math.Abs( (Int32) ( ( ticks / TimeSpan.TicksPerMinute ) % PgSQLInte
 1722            .WriteASCIIByte( array, ref offset, SEPARATOR ) // ':'
 1723            .WriteIntegerTextual( array, ref offset, Math.Abs( (Int32) ( ( ticks / TimeSpan.TicksPerSecond ) % PgSQLInte
 1724
 1725         var microSeconds = Math.Abs( PgSQLInterval.CalcMicroseconds( ticks ) );
 1726         if ( microSeconds != 0 )
 1727         {
 1728            encoding
 1729               .WriteASCIIByte( array, ref offset, MICRO_SEPARATOR ) // '.'
 1730               .WriteFractionIntegerTextual( array, ref offset, microSeconds, MICRO_PRECISION );
 1731         }
 1732      }
 1733
 1734      public Int32 GetTextByteCount( IEncodingInfo encoding )
 1735      {
 1736         return GetTextByteCount( encoding, this._ticks );
 1737      }
 1738
 1739      public void WriteTextBytes( IEncodingInfo encoding, Byte[] array, ref Int32 offset )
 1740      {
 1741         WriteTextBytes( encoding, this._ticks, array, ref offset );
 1742      }
 1743
 1744      public static PgSQLTime ParseBinaryText( IEncodingInfo encoding, Byte[] array, ref Int32 offset, Int32 count )
 1745      {
 1746         var max = offset + count;
 1747         var hours = encoding.ParseInt32Textual( array, ref offset, (HOUR_CHAR_COUNT, true) );
 1748         var minutes = encoding.EqualsOrThrow( array, ref offset, SEPARATOR ).ParseInt32Textual( array, ref offset, (MIN
 1749         var seconds = encoding.EqualsOrThrow( array, ref offset, SEPARATOR ).ParseInt32Textual( array, ref offset, (SEC
 1750         var oldIdx = offset;
 1751         if ( encoding.TryParseOptionalNumber( array, ref offset, MICRO_SEPARATOR, (MICRO_PRECISION, false), max, out In
 1752         {
 1753            // When calculating trailing zeroes, we must take the prefix (MICRO_SEPARATOR) into account
 1754            var trailingZeroesCount = MICRO_PRECISION - ( offset - oldIdx - encoding.BytesPerASCIICharacter );
 1755            while ( trailingZeroesCount > 0 )
 1756            {
 1757               micros *= 10;
 1758               --trailingZeroesCount;
 1759            }
 1760         }
 1761
 1762         return new PgSQLTime( hours, minutes, seconds, micros );
 1763      }
 1764
 1765      public override String ToString()
 1766      {
 1767         var sb = new StringBuilder();
 1768         PgSQLInterval.AppendTimeInformation( sb, this._ticks );
 1769         return sb.ToString();
 1770      }
 1771
 1772      public static PgSQLTime Parse( String str )
 1773      {
 1774         PgSQLTime result; Exception error;
 1775         TryParse( str, out result, out error );
 1776         if ( error != null )
 1777         {
 1778            throw error;
 1779         }
 1780         return result;
 1781      }
 1782
 1783      public static Boolean TryParse( String str, out PgSQLTime result )
 1784      {
 1785         Exception error;
 1786         return TryParse( str, out result, out error );
 1787      }
 1788
 1789      internal static Boolean TryParse( String str, out PgSQLTime result, out Exception error )
 1790      {
 1791         if ( str == null )
 1792         {
 1793            result = default( PgSQLTime );
 1794            error = new ArgumentNullException( "String" );
 1795         }
 1796         else
 1797         {
 1798            error = null;
 1799            Int32 hours, minutes; Decimal seconds; Boolean isNegative;
 1800            PgSQLInterval.ParseTime( str, 0, out hours, out minutes, out seconds, out isNegative, ref error, true );
 1801            if ( hours < 0 || hours > 24 || minutes < 0 || minutes > 59 || seconds < 0m || seconds >= 60m || ( hours == 
 1802            {
 1803               error = new FormatException( "One of the hours, minutes, or seconds (" + hours + ":" + minutes + ":" + se
 1804            }
 1805
 1806            result = error == null ?
 1807               new PgSQLTime( hours, minutes, seconds ) :
 1808               default( PgSQLTime );
 1809         }
 1810         return error == null;
 1811      }
 1812
 1813      #endregion
 1814
 1815   }
 1816
 1817   public struct PgSQLTimeZone : IEquatable<PgSQLTimeZone>, IComparable, IComparable<PgSQLTimeZone>
 1818   {
 1819      #region Consts
 1820
 1821      private const Int32 MINUTES_PER_HOUR = (Int32) PgSQLInterval.MINUTES_PER_HOUR;
 1822      private const Int32 SECONDS_PER_MINUTE = (Int32) PgSQLInterval.SECONDS_PER_MINUTE;
 1823
 1824      #endregion
 1825
 1826      #region Static
 1827
 1828      public static readonly PgSQLTimeZone UTC = new PgSQLTimeZone( 0 );
 1829
 1830      public static PgSQLTimeZone CurrentTimeZone
 1831      {
 1832         get
 1833         {
 1834            return new PgSQLTimeZone( TimeZoneInfo.Local.GetUtcOffset( DateTime.Now ) );
 1835         }
 1836      }
 1837
 1838      public static PgSQLTimeZone GetSolarTimeZone( Decimal longitude )
 1839      {
 1840         return new PgSQLTimeZone( (Int64) ( longitude / 15m * TimeSpan.TicksPerHour ) );
 1841      }
 1842
 1843      public static PgSQLTimeZone GetLocalTimeZone( PgSQLDate date )
 1844      {
 1845         return new PgSQLTimeZone( TimeZoneInfo.Local.GetUtcOffset(
 1846            date.Year >= 1902 && date.Year < 2038 ?
 1847               (DateTime) date :
 1848               new DateTime( 2000, date.Month, date.Day )
 1849            ) );
 1850      }
 1851
 1852      #endregion
 1853
 1854      #region Fields
 1855
 1856      private readonly Int32 _totalSeconds;
 1857
 1858      #endregion
 1859
 1860      #region Constructors
 1861
 1862      public PgSQLTimeZone( Int64 ticks )
 1863         : this( (Int32) ( ticks / TimeSpan.TicksPerSecond ) )
 1864      {
 1865      }
 1866
 1867      public PgSQLTimeZone( Int32 hours, Int32 minutes )
 1868         : this( hours, minutes, 0 )
 1869      {
 1870      }
 1871
 1872      public PgSQLTimeZone( Int32 hours, Int32 minutes, Int32 seconds )
 1873         : this( hours * MINUTES_PER_HOUR * SECONDS_PER_MINUTE + minutes * SECONDS_PER_MINUTE + seconds )
 1874      {
 1875      }
 1876
 1877      public PgSQLTimeZone( PgSQLTimeZone other )
 1878         : this( other._totalSeconds )
 1879      {
 1880      }
 1881
 1882      public PgSQLTimeZone( PgSQLInterval interval )
 1883         : this( interval.Ticks )
 1884      {
 1885      }
 1886
 1887      public PgSQLTimeZone( TimeSpan timeSpan )
 1888         : this( timeSpan.Ticks )
 1889      {
 1890      }
 1891
 1892      private PgSQLTimeZone( Int32 seconds )
 1893      {
 1894         this._totalSeconds = seconds;
 1895      }
 1896
 1897      #endregion
 1898
 1899      #region Properties
 1900
 1901      public Int32 Hours
 1902      {
 1903         get
 1904         {
 1905            return this._totalSeconds / MINUTES_PER_HOUR / SECONDS_PER_MINUTE;
 1906         }
 1907      }
 1908
 1909      public Int32 Minutes
 1910      {
 1911         get
 1912         {
 1913            return ( this._totalSeconds / MINUTES_PER_HOUR ) % SECONDS_PER_MINUTE;
 1914         }
 1915      }
 1916
 1917      public Int32 Seconds
 1918      {
 1919         get
 1920         {
 1921            return this._totalSeconds % SECONDS_PER_MINUTE;
 1922         }
 1923      }
 1924
 1925      public Int32 TotalSeconds
 1926      {
 1927         get
 1928         {
 1929            return this._totalSeconds;
 1930         }
 1931      }
 1932
 1933      #endregion
 1934
 1935      #region Comparison
 1936
 1937      public Boolean Equals( PgSQLTimeZone other )
 1938      {
 1939         return this._totalSeconds == other._totalSeconds;
 1940      }
 1941
 1942      public override Boolean Equals( Object obj )
 1943      {
 1944         return obj != null && obj is PgSQLTimeZone && this.Equals( (PgSQLTimeZone) obj );
 1945      }
 1946
 1947      public override Int32 GetHashCode()
 1948      {
 1949         return this._totalSeconds.GetHashCode();
 1950      }
 1951
 1952      Int32 IComparable.CompareTo( Object obj )
 1953      {
 1954         if ( obj == null )
 1955         {
 1956            // This is always 'greater' than null
 1957            return 1;
 1958         }
 1959         else if ( obj is PgSQLTimeZone )
 1960         {
 1961            return this.CompareTo( (PgSQLTimeZone) obj );
 1962         }
 1963         else
 1964         {
 1965            throw new ArgumentException( "Given object must be of type " + this.GetType() + " or null." );
 1966         }
 1967      }
 1968
 1969      public Int32 CompareTo( PgSQLTimeZone other )
 1970      {
 1971         // Note: +01:00 is less than -01:00, so we have to invert the result (accomplished by comparing other to this i
 1972         return other._totalSeconds.CompareTo( this._totalSeconds );
 1973      }
 1974
 1975      #endregion
 1976
 1977      #region Casts
 1978
 1979      public static implicit operator PgSQLTimeZone( PgSQLInterval interval )
 1980      {
 1981         return new PgSQLTimeZone( interval );
 1982      }
 1983
 1984      public static implicit operator PgSQLInterval( PgSQLTimeZone timeZone )
 1985      {
 1986         return new PgSQLInterval( timeZone._totalSeconds * TimeSpan.TicksPerSecond );
 1987      }
 1988
 1989      public static implicit operator PgSQLTimeZone( TimeSpan interval )
 1990      {
 1991         return new PgSQLTimeZone( (PgSQLInterval) interval );
 1992      }
 1993
 1994      public static implicit operator TimeSpan( PgSQLTimeZone timeZone )
 1995      {
 1996         return new TimeSpan( timeZone._totalSeconds * TimeSpan.TicksPerSecond );
 1997      }
 1998
 1999      #endregion
 2000
 2001      #region Operators
 2002
 2003      public static Boolean operator ==( PgSQLTimeZone x, PgSQLTimeZone y )
 2004      {
 2005         return x.Equals( y );
 2006      }
 2007
 2008      public static Boolean operator !=( PgSQLTimeZone x, PgSQLTimeZone y )
 2009      {
 2010         return !( x == y );
 2011      }
 2012
 2013      public static Boolean operator <( PgSQLTimeZone x, PgSQLTimeZone y )
 2014      {
 2015         return x._totalSeconds < y._totalSeconds;
 2016      }
 2017
 2018      public static Boolean operator <=( PgSQLTimeZone x, PgSQLTimeZone y )
 2019      {
 2020         return x._totalSeconds <= y._totalSeconds;
 2021      }
 2022
 2023      public static Boolean operator >( PgSQLTimeZone x, PgSQLTimeZone y )
 2024      {
 2025         return !( x <= y );
 2026      }
 2027
 2028      public static Boolean operator >=( PgSQLTimeZone x, PgSQLTimeZone y )
 2029      {
 2030         return !( x < y );
 2031      }
 2032
 2033      public static PgSQLTimeZone operator -( PgSQLTimeZone tz )
 2034      {
 2035         return new PgSQLTimeZone( -tz._totalSeconds );
 2036      }
 2037
 2038      public static PgSQLTimeZone operator +( PgSQLTimeZone tz )
 2039      {
 2040         return tz;
 2041      }
 2042
 2043      #endregion
 2044
 2045      #region To and from string
 2046
 2047      private const Byte PREFIX_POS = (Byte) '+';
 2048      private const Byte PREFIX_NEG = (Byte) '-';
 2049      private const Byte SEPARATOR = (Byte) ':';
 2050      private const Int32 HOURS_CHAR_COUNT = 2;
 2051      private const Int32 MINUTES_CHAR_COUNT = 2;
 2052      private const Int32 SECONDS_CHAR_COUNT = 2;
 2053
 2054      public Int32 GetTextByteCount( IEncodingInfo encoding )
 2055      {
 2056         var retVal = 3 * encoding.BytesPerASCIICharacter; // +/-, and always 2 digits for hours
 2057         var mins = this.Minutes;
 2058         var secs = this.Seconds;
 2059         if ( mins != 0 || secs != 0 )
 2060         {
 2061            retVal += 3 * encoding.BytesPerASCIICharacter; // For minutes, always
 2062            if ( secs != 0 )
 2063            {
 2064               retVal += 3 * encoding.BytesPerASCIICharacter; // For seconds
 2065            }
 2066         }
 2067
 2068         return retVal;
 2069      }
 2070
 2071      public void WriteTextBytes( IEncodingInfo encoding, Byte[] array, ref Int32 offset )
 2072      {
 2073         var hours = this.Hours;
 2074         encoding.WriteASCIIByte( array, ref offset, hours > 0 ? PREFIX_POS : PREFIX_NEG ); // +/-
 2075         hours = Math.Abs( hours );
 2076         encoding.WriteIntegerTextual( array, ref offset, hours, HOURS_CHAR_COUNT );
 2077         var mins = this.Minutes;
 2078         var secs = this.Seconds;
 2079         if ( mins != 0 || secs != 0 )
 2080         {
 2081            encoding
 2082               .WriteASCIIByte( array, ref offset, SEPARATOR ) // ':'
 2083               .WriteIntegerTextual( array, ref offset, Math.Abs( mins ), MINUTES_CHAR_COUNT );
 2084            if ( secs != 0 )
 2085            {
 2086               encoding
 2087                  .WriteASCIIByte( array, ref offset, SEPARATOR ) // ':'
 2088                  .WriteIntegerTextual( array, ref offset, Math.Abs( secs ), SECONDS_CHAR_COUNT );
 2089            }
 2090         }
 2091      }
 2092
 2093      internal static PgSQLTimeZone ParseBinaryText( IEncodingInfo encoding, Byte[] array, ref Int32 offset, Int32 count
 2094      {
 2095         var max = offset + count;
 2096         var prefix = encoding.ReadASCIIByte( array, ref offset );
 2097         if ( prefix != PREFIX_POS && prefix != PREFIX_NEG )
 2098         {
 2099            throw new FormatException( $"Expected timezone string to start with either '{PREFIX_POS}' or '{PREFIX_NEG}' 
 2100         }
 2101         var hours = encoding.ParseInt32Textual( array, ref offset, (HOURS_CHAR_COUNT, true) );
 2102         encoding.TryParseOptionalNumber( array, ref offset, SEPARATOR, (MINUTES_CHAR_COUNT, true), max, out Int32 minut
 2103         encoding.TryParseOptionalNumber( array, ref offset, SEPARATOR, (SECONDS_CHAR_COUNT, true), max, out Int32 secon
 2104         return new PgSQLTimeZone( hours, minutes, seconds );
 2105      }
 2106
 2107      public override String ToString()
 2108      {
 2109         var sb = new StringBuilder( this.Hours.ToString( "+0#;-0#" ) );
 2110         if ( this.Minutes != 0 || this.Seconds != 0 )
 2111         {
 2112            sb.Append( ':' ).Append( Math.Abs( this.Minutes ).ToString( "D2" ) );
 2113            if ( this.Seconds != 0 )
 2114            {
 2115               sb.Append( ':' ).Append( Math.Abs( this.Seconds ).ToString( "D2" ) );
 2116            }
 2117         }
 2118         return sb.ToString();
 2119      }
 2120
 2121      public static PgSQLTimeZone Parse( String str )
 2122      {
 2123         PgSQLTimeZone result; Exception error;
 2124         TryParse( str, out result, out error );
 2125         if ( error != null )
 2126         {
 2127            throw error;
 2128         }
 2129         return result;
 2130      }
 2131
 2132      public static Boolean TryParse( String str, out PgSQLTimeZone result )
 2133      {
 2134         Exception error;
 2135         return TryParse( str, out result, out error );
 2136      }
 2137
 2138      internal static Boolean TryParse( String str, out PgSQLTimeZone result, out Exception error )
 2139      {
 2140         if ( str == null )
 2141         {
 2142            result = default( PgSQLTimeZone );
 2143            error = new ArgumentNullException( "String" );
 2144         }
 2145         else
 2146         {
 2147            error = null;
 2148            var totalSeconds = 0;
 2149            if ( str.Length <= 1 )
 2150            {
 2151               error = new FormatException( "Too short string." );
 2152            }
 2153            else
 2154            {
 2155
 2156               if ( error == null )
 2157               {
 2158                  Int32 hours, minutes; Decimal seconds; Boolean isNegative;
 2159                  PgSQLInterval.ParseTime( str, 0, out hours, out minutes, out seconds, out isNegative, ref error, true 
 2160
 2161                  if ( error == null )
 2162                  {
 2163                     seconds = Decimal.Truncate( seconds );
 2164                     if ( seconds >= new Decimal( Int32.MinValue ) && seconds <= new Decimal( Int32.MaxValue ) )
 2165                     {
 2166                        totalSeconds = hours * MINUTES_PER_HOUR * SECONDS_PER_MINUTE + minutes * SECONDS_PER_MINUTE + (I
 2167                     }
 2168                     else
 2169                     {
 2170                        error = new FormatException( "Seconds (" + seconds + ") are out of range for Int32." );
 2171                     }
 2172                  }
 2173               }
 2174            }
 2175
 2176            result = error == null ?
 2177               new PgSQLTimeZone( totalSeconds ) :
 2178               default( PgSQLTimeZone );
 2179         }
 2180         return error == null;
 2181      }
 2182
 2183      #endregion
 2184   }
 2185
 2186   //public interface IPgSQLTimeTZ : IPgSQLTime
 2187   //{
 2188   //   #region Properties
 2189
 2190   //   PgSQLTime LocalTime { get; }
 2191
 2192   //   PgSQLTimeZone TimeZone { get; }
 2193
 2194   //   PgSQLTime UTCTime { get; }
 2195
 2196   //   #endregion
 2197   //}
 2198
 2199   public struct PgSQLTimeTZ : IEquatable<PgSQLTimeTZ>, IComparable, IComparable<PgSQLTimeTZ>//, IPgSQLTimeTZ
 2200   {
 2201      #region Static
 2202
 2203      public static readonly PgSQLTimeTZ AllBalls = new PgSQLTimeTZ( PgSQLTime.AllBalls, PgSQLTimeZone.UTC );
 2204
 2205      public static PgSQLTimeTZ Now
 2206      {
 2207         get
 2208         {
 2209            return new PgSQLTimeTZ( PgSQLTime.Now );
 2210         }
 2211      }
 2212
 2213      public static PgSQLTimeTZ GetLocalMidnightFor( PgSQLDate date )
 2214      {
 2215         return new PgSQLTimeTZ( PgSQLTime.AllBalls, PgSQLTimeZone.GetLocalTimeZone( date ) );
 2216      }
 2217
 2218      #endregion
 2219
 2220      #region Fields
 2221
 2222      private readonly PgSQLTime _localTime;
 2223      private readonly PgSQLTimeZone _timeZone;
 2224
 2225      #endregion
 2226
 2227      #region Constructors
 2228
 2229      public PgSQLTimeTZ( PgSQLTime localTime, PgSQLTimeZone timeZone )
 2230      {
 2231         _localTime = localTime;
 2232         _timeZone = timeZone;
 2233      }
 2234
 2235      public PgSQLTimeTZ( PgSQLTime localTime )
 2236         : this( localTime, PgSQLTimeZone.CurrentTimeZone )
 2237      {
 2238      }
 2239
 2240      public PgSQLTimeTZ( Int64 ticks )
 2241         : this( new PgSQLTime( ticks ) )
 2242      {
 2243      }
 2244
 2245      public PgSQLTimeTZ( TimeSpan time )
 2246         : this( new PgSQLTime( time ) )
 2247      {
 2248      }
 2249
 2250      public PgSQLTimeTZ( PgSQLInterval time )
 2251         : this( new PgSQLTime( time ) )
 2252      {
 2253      }
 2254
 2255      public PgSQLTimeTZ( PgSQLTimeTZ copyFrom )
 2256         : this( copyFrom._localTime, copyFrom._timeZone )
 2257      {
 2258      }
 2259
 2260      public PgSQLTimeTZ( Int32 hours, Int32 minutes, Int32 seconds )
 2261         : this( new PgSQLTime( hours, minutes, seconds ) )
 2262      {
 2263      }
 2264
 2265      public PgSQLTimeTZ( Int32 hours, Int32 minutes, Int32 seconds, Int32 microseconds )
 2266         : this( new PgSQLTime( hours, minutes, seconds, microseconds ) )
 2267      {
 2268      }
 2269
 2270      public PgSQLTimeTZ( Int32 hours, Int32 minutes, Decimal seconds )
 2271         : this( new PgSQLTime( hours, minutes, seconds ) )
 2272      {
 2273      }
 2274
 2275      public PgSQLTimeTZ( long ticks, PgSQLTimeZone timeZone )
 2276         : this( new PgSQLTime( ticks ), timeZone )
 2277      {
 2278      }
 2279
 2280      public PgSQLTimeTZ( TimeSpan time, PgSQLTimeZone timeZone )
 2281         : this( new PgSQLTime( time ), timeZone )
 2282      {
 2283      }
 2284
 2285      public PgSQLTimeTZ( PgSQLInterval time, PgSQLTimeZone timeZone )
 2286         : this( new PgSQLTime( time ), timeZone )
 2287      {
 2288      }
 2289
 2290      public PgSQLTimeTZ( Int32 hours, Int32 minutes, Int32 seconds, PgSQLTimeZone timeZone )
 2291         : this( new PgSQLTime( hours, minutes, seconds ), timeZone )
 2292      {
 2293      }
 2294
 2295      public PgSQLTimeTZ( Int32 hours, Int32 minutes, Int32 seconds, Int32 microseconds, PgSQLTimeZone timeZone )
 2296         : this( new PgSQLTime( hours, minutes, seconds, microseconds ), timeZone )
 2297      {
 2298      }
 2299
 2300      public PgSQLTimeTZ( Int32 hours, Int32 minutes, Decimal seconds, PgSQLTimeZone timeZone )
 2301         : this( new PgSQLTime( hours, minutes, seconds ), timeZone )
 2302      {
 2303      }
 2304
 2305      #endregion
 2306
 2307      #region Properties
 2308
 2309      public PgSQLTime LocalTime
 2310      {
 2311         get
 2312         {
 2313            return this._localTime;
 2314         }
 2315      }
 2316
 2317      public PgSQLTimeZone TimeZone
 2318      {
 2319         get
 2320         {
 2321            return this._timeZone;
 2322         }
 2323      }
 2324
 2325      public PgSQLTime UTCTime
 2326      {
 2327         get
 2328         {
 2329            return this.AtTimeZone( PgSQLTimeZone.UTC ).LocalTime;
 2330         }
 2331      }
 2332
 2333      public Int32 Microseconds
 2334      {
 2335         get
 2336         {
 2337            return this._localTime.Microseconds;
 2338         }
 2339      }
 2340
 2341      public Int32 Milliseconds
 2342      {
 2343         get
 2344         {
 2345            return this._localTime.Milliseconds;
 2346         }
 2347      }
 2348
 2349      public Int32 Seconds
 2350      {
 2351         get
 2352         {
 2353            return this._localTime.Seconds;
 2354         }
 2355      }
 2356
 2357      public Int32 Minutes
 2358      {
 2359         get
 2360         {
 2361            return this._localTime.Minutes;
 2362         }
 2363      }
 2364
 2365      public Int32 Hours
 2366      {
 2367         get
 2368         {
 2369            return this._localTime.Hours;
 2370         }
 2371      }
 2372
 2373      public Int64 Ticks
 2374      {
 2375         get
 2376         {
 2377            return this._localTime.Ticks;
 2378         }
 2379      }
 2380
 2381      #endregion
 2382
 2383      #region Comparison
 2384
 2385      public Boolean Equals( PgSQLTimeTZ other )
 2386      {
 2387         return this._localTime.Equals( other._localTime ) && this._timeZone.Equals( other._timeZone );
 2388      }
 2389
 2390      public override Boolean Equals( Object obj )
 2391      {
 2392         return obj != null && obj is PgSQLTimeTZ && this.Equals( (PgSQLTimeTZ) obj );
 2393      }
 2394
 2395      public override Int32 GetHashCode()
 2396      {
 2397         // Like in point
 2398         unchecked
 2399         {
 2400            return ( 17 * 23 + this._localTime.GetHashCode() ) * 23 + this._timeZone.GetHashCode();
 2401         }
 2402      }
 2403
 2404      Int32 IComparable.CompareTo( Object obj )
 2405      {
 2406         if ( obj == null )
 2407         {
 2408            // This is always 'greater' than null
 2409            return 1;
 2410         }
 2411         else if ( obj is PgSQLTimeTZ )
 2412         {
 2413            return this.CompareTo( (PgSQLTimeTZ) obj );
 2414         }
 2415         else
 2416         {
 2417            throw new ArgumentException( "Given object must be of type " + this.GetType() + " or null." );
 2418         }
 2419      }
 2420
 2421      public Int32 CompareTo( PgSQLTimeTZ other )
 2422      {
 2423         var utcCompare = this.UTCTime.CompareTo( other.UTCTime );
 2424         return utcCompare == 0 ? this._timeZone.CompareTo( other._timeZone ) : utcCompare;
 2425      }
 2426
 2427      #endregion
 2428
 2429      #region Arithmetics
 2430
 2431      public PgSQLTimeTZ Normalize()
 2432      {
 2433         return new PgSQLTimeTZ( this._localTime.Normalize(), this._timeZone );
 2434      }
 2435
 2436      public PgSQLTimeTZ AtTimeZone( PgSQLTimeZone timeZone )
 2437      {
 2438         return new PgSQLTimeTZ( this._localTime - this._timeZone + timeZone, timeZone );
 2439      }
 2440
 2441      public PgSQLTimeTZ AtTimeZone( PgSQLTimeZone timeZone, out Int32 overflow )
 2442      {
 2443         return new PgSQLTimeTZ( this._localTime.Add( timeZone - (PgSQLInterval) ( _timeZone ), out overflow ), timeZone
 2444      }
 2445
 2446      public PgSQLTimeTZ Add( PgSQLInterval interval )
 2447      {
 2448         return new PgSQLTimeTZ( _localTime.Add( interval ), _timeZone );
 2449      }
 2450
 2451      internal PgSQLTimeTZ Add( PgSQLInterval interval, out Int32 overflow )
 2452      {
 2453         return new PgSQLTimeTZ( _localTime.Add( interval, out overflow ), _timeZone );
 2454      }
 2455
 2456      public PgSQLTimeTZ Subtract( PgSQLInterval interval )
 2457      {
 2458         return new PgSQLTimeTZ( _localTime.Subtract( interval ), _timeZone );
 2459      }
 2460
 2461      public PgSQLInterval Subtract( PgSQLTimeTZ earlier )
 2462      {
 2463         return _localTime.Subtract( earlier.AtTimeZone( _timeZone )._localTime );
 2464      }
 2465
 2466      #endregion
 2467
 2468      #region Casts
 2469
 2470      public static explicit operator DateTime( PgSQLTimeTZ x )
 2471      {
 2472         return new DateTime( x.AtTimeZone( PgSQLTimeZone.CurrentTimeZone ).Ticks, DateTimeKind.Local );
 2473      }
 2474
 2475      public static explicit operator PgSQLTimeTZ( DateTime x )
 2476      {
 2477         return new PgSQLTimeTZ( new PgSQLTime( x ) );
 2478      }
 2479
 2480      public static explicit operator TimeSpan( PgSQLTimeTZ x )
 2481      {
 2482         return (TimeSpan) x.LocalTime;
 2483      }
 2484
 2485      public static explicit operator PgSQLTimeTZ( TimeSpan x )
 2486      {
 2487         return new PgSQLTimeTZ( (PgSQLTime) x );
 2488      }
 2489
 2490      #endregion
 2491
 2492      #region Operators
 2493
 2494      public static Boolean operator ==( PgSQLTimeTZ x, PgSQLTimeTZ y )
 2495      {
 2496         return x.Equals( y );
 2497      }
 2498
 2499      public static Boolean operator !=( PgSQLTimeTZ x, PgSQLTimeTZ y )
 2500      {
 2501         return !( x == y );
 2502      }
 2503
 2504      public static Boolean operator <( PgSQLTimeTZ x, PgSQLTimeTZ y )
 2505      {
 2506         return x.CompareTo( y ) < 0;
 2507      }
 2508
 2509      public static Boolean operator <=( PgSQLTimeTZ x, PgSQLTimeTZ y )
 2510      {
 2511         return x.CompareTo( y ) <= 0;
 2512      }
 2513
 2514      public static Boolean operator >( PgSQLTimeTZ x, PgSQLTimeTZ y )
 2515      {
 2516         return !( x <= y );
 2517      }
 2518
 2519      public static Boolean operator >=( PgSQLTimeTZ x, PgSQLTimeTZ y )
 2520      {
 2521         return !( x < y );
 2522      }
 2523
 2524      public static PgSQLTimeTZ operator +( PgSQLTimeTZ time, PgSQLInterval interval )
 2525      {
 2526         return time.Add( interval );
 2527      }
 2528
 2529      public static PgSQLTimeTZ operator +( PgSQLInterval interval, PgSQLTimeTZ time )
 2530      {
 2531         return time + interval;
 2532      }
 2533
 2534      public static PgSQLTimeTZ operator -( PgSQLTimeTZ time, PgSQLInterval interval )
 2535      {
 2536         return time.Subtract( interval );
 2537      }
 2538
 2539      public static PgSQLInterval operator -( PgSQLTimeTZ later, PgSQLTimeTZ earlier )
 2540      {
 2541         return later.Subtract( earlier );
 2542      }
 2543
 2544      #endregion
 2545
 2546      #region To and from string
 2547
 2548      public Int32 GetTextByteCount( IEncodingInfo encoding )
 2549      {
 2550         return this._localTime.GetTextByteCount( encoding ) + this._timeZone.GetTextByteCount( encoding );
 2551      }
 2552
 2553      public void WriteTextBytes( IEncodingInfo encoding, Byte[] array, ref Int32 offset )
 2554      {
 2555         this._localTime.WriteTextBytes( encoding, array, ref offset );
 2556         this._timeZone.WriteTextBytes( encoding, array, ref offset );
 2557      }
 2558
 2559      public static PgSQLTimeTZ ParseBinaryText( IEncodingInfo encoding, Byte[] array, ref Int32 offset, Int32 count )
 2560      {
 2561         var oldIdx = offset;
 2562         var time = PgSQLTime.ParseBinaryText( encoding, array, ref offset, count );
 2563         count -= offset - oldIdx;
 2564         return new PgSQLTimeTZ(
 2565            time,
 2566            PgSQLTimeZone.ParseBinaryText( encoding, array, ref offset, count )
 2567            );
 2568      }
 2569
 2570
 2571      public override String ToString()
 2572      {
 2573         return this._localTime.ToString() + this._timeZone.ToString();
 2574      }
 2575
 2576      public static PgSQLTimeTZ Parse( String str )
 2577      {
 2578         PgSQLTimeTZ result; Exception error;
 2579         TryParse( str, out result, out error );
 2580         if ( error != null )
 2581         {
 2582            throw error;
 2583         }
 2584         return result;
 2585      }
 2586
 2587      public static Boolean TryParse( String str, out PgSQLTimeTZ result )
 2588      {
 2589         Exception error;
 2590         return TryParse( str, out result, out error );
 2591      }
 2592
 2593      internal static Boolean TryParse( String str, out PgSQLTimeTZ result, out Exception error )
 2594      {
 2595         if ( str == null )
 2596         {
 2597            result = default( PgSQLTimeTZ );
 2598            error = new ArgumentNullException( "String" );
 2599         }
 2600         else
 2601         {
 2602            error = null;
 2603            //Search for timezone offset + or -
 2604            var idx = Math.Max( str.IndexOf( '+' ), str.IndexOf( '-' ) );
 2605            if ( idx < 0 )
 2606            {
 2607               error = new FormatException( "Missing time zone information." );
 2608            }
 2609
 2610            PgSQLTime time; PgSQLTimeZone tz;
 2611            result = error == null && PgSQLTime.TryParse( str.Substring( 0, idx ), out time, out error ) && PgSQLTimeZon
 2612               new PgSQLTimeTZ( time, tz ) :
 2613               default( PgSQLTimeTZ );
 2614         }
 2615         return error == null;
 2616      }
 2617
 2618      #endregion
 2619
 2620   }
 2621
 2622   //public interface IPgSQLTimestamp
 2623   //{
 2624   //   #region Properties
 2625
 2626   //   Boolean IsFinite { get; }
 2627
 2628   //   Boolean IsInfinity { get; }
 2629
 2630   //   Boolean IsMinusInfinity { get; }
 2631
 2632   //   PgSQLDate Date { get; }
 2633
 2634   //   #endregion
 2635   //}
 2636
 2637   public struct PgSQLTimestamp : IEquatable<PgSQLTimestamp>, IComparable, IComparable<PgSQLTimestamp>//, IPgSQLDate, IP
 2638   {
 2639      #region Inner types
 2640
 2641      internal enum TSKind : byte
 2642      {
 2643         Normal,
 2644         Infinity,
 2645         MinusInfinity,
 2646      }
 2647
 2648      #endregion
 2649
 2650      #region Static
 2651
 2652      public static readonly PgSQLTimestamp Epoch = new PgSQLTimestamp( PgSQLDate.Epoch );
 2653      public static readonly PgSQLTimestamp Era = new PgSQLTimestamp( PgSQLDate.Era );
 2654      public static readonly PgSQLTimestamp Infinity = new PgSQLTimestamp( TSKind.Infinity );
 2655      public static readonly PgSQLTimestamp MinusInfinity = new PgSQLTimestamp( TSKind.MinusInfinity );
 2656
 2657      public static PgSQLTimestamp Now
 2658      {
 2659         get
 2660         {
 2661            return new PgSQLTimestamp( PgSQLDate.Now, PgSQLTime.Now );
 2662         }
 2663      }
 2664
 2665      public static PgSQLTimestamp Today
 2666      {
 2667         get
 2668         {
 2669            return new PgSQLTimestamp( PgSQLDate.Now );
 2670         }
 2671      }
 2672
 2673      public static PgSQLTimestamp Yesterday
 2674      {
 2675         get
 2676         {
 2677            return new PgSQLTimestamp( PgSQLDate.Yesterday );
 2678         }
 2679      }
 2680
 2681      public static PgSQLTimestamp Tomorrow
 2682      {
 2683         get
 2684         {
 2685            return new PgSQLTimestamp( PgSQLDate.Tomorrow );
 2686         }
 2687      }
 2688
 2689      #endregion
 2690
 2691      #region Fields
 2692
 2693      private readonly PgSQLDate _date;
 2694      private readonly PgSQLTime _time;
 2695      private readonly TSKind _kind;
 2696
 2697      #endregion
 2698
 2699      #region Constructors
 2700
 2701      private PgSQLTimestamp( TSKind kind )
 2702      {
 2703         if ( kind != TSKind.Infinity && kind != TSKind.MinusInfinity )
 2704         {
 2705            throw new InvalidOperationException( "This constructor may only be used to create infinity or minus infinity
 2706         }
 2707         this._kind = kind;
 2708         this._date = PgSQLDate.Era;
 2709         this._time = PgSQLTime.AllBalls;
 2710      }
 2711
 2712      public PgSQLTimestamp( PgSQLDate date, PgSQLTime time )
 2713      {
 2714         this._kind = TSKind.Normal;
 2715         this._date = date;
 2716         this._time = time;
 2717      }
 2718
 2719      public PgSQLTimestamp( PgSQLDate date )
 2720         : this( date, PgSQLTime.AllBalls )
 2721      {
 2722      }
 2723
 2724      public PgSQLTimestamp( Int32 year, Int32 month, Int32 day, Int32 hours, Int32 minutes, Int32 seconds )
 2725         : this( new PgSQLDate( year, month, day ), new PgSQLTime( hours, minutes, seconds ) )
 2726      {
 2727      }
 2728
 2729      public PgSQLTimestamp( Int32 year, Int32 month, Int32 day, Int32 hours, Int32 minutes, Decimal seconds )
 2730         : this( new PgSQLDate( year, month, day ), new PgSQLTime( hours, minutes, seconds ) )
 2731      {
 2732      }
 2733
 2734      #endregion
 2735
 2736      #region Properties
 2737
 2738      public Boolean IsFinite
 2739      {
 2740         get
 2741         {
 2742            return this._kind == TSKind.Normal;
 2743         }
 2744      }
 2745
 2746      public Boolean IsInfinity
 2747      {
 2748         get
 2749         {
 2750            return this._kind == TSKind.Infinity;
 2751         }
 2752      }
 2753
 2754      public Boolean IsMinusInfinity
 2755      {
 2756         get
 2757         {
 2758            return this._kind == TSKind.MinusInfinity;
 2759         }
 2760      }
 2761
 2762      public PgSQLDate Date
 2763      {
 2764         get
 2765         {
 2766            return this._date;
 2767         }
 2768      }
 2769
 2770      public PgSQLTime Time
 2771      {
 2772         get
 2773         {
 2774            return this._time;
 2775         }
 2776      }
 2777
 2778      public Int32 DayOfYear
 2779      {
 2780         get
 2781         {
 2782            return this._date.DayOfYear;
 2783         }
 2784      }
 2785
 2786      public Int32 Year
 2787      {
 2788         get
 2789         {
 2790            return this._date.Year;
 2791         }
 2792      }
 2793
 2794      public Int32 Month
 2795      {
 2796         get
 2797         {
 2798            return this._date.Month;
 2799         }
 2800      }
 2801
 2802      public Int32 Day
 2803      {
 2804         get
 2805         {
 2806            return this._date.Day;
 2807         }
 2808      }
 2809
 2810      public DayOfWeek DayOfWeek
 2811      {
 2812         get
 2813         {
 2814            return this._date.DayOfWeek;
 2815         }
 2816      }
 2817
 2818      public Int32 DaysSinceEra
 2819      {
 2820         get
 2821         {
 2822            return this._date.DaysSinceEra;
 2823         }
 2824      }
 2825
 2826      public Boolean IsLeapYear
 2827      {
 2828         get
 2829         {
 2830            return this._date.IsLeapYear;
 2831         }
 2832      }
 2833
 2834      public Int64 Ticks
 2835      {
 2836         get
 2837         {
 2838            return this._time.Ticks;
 2839         }
 2840      }
 2841
 2842      public Int32 Microseconds
 2843      {
 2844         get
 2845         {
 2846            return this._time.Microseconds;
 2847         }
 2848      }
 2849
 2850      public Int32 Milliseconds
 2851      {
 2852         get
 2853         {
 2854            return this._time.Milliseconds;
 2855         }
 2856      }
 2857
 2858      public Int32 Seconds
 2859      {
 2860         get
 2861         {
 2862            return this._time.Seconds;
 2863         }
 2864      }
 2865
 2866      public Int32 Minutes
 2867      {
 2868         get
 2869         {
 2870            return this._time.Minutes;
 2871         }
 2872      }
 2873
 2874      public Int32 Hours
 2875      {
 2876         get
 2877         {
 2878            return this._time.Hours;
 2879         }
 2880      }
 2881
 2882      #endregion
 2883
 2884      #region Arithmetics
 2885
 2886      public PgSQLTimestamp AddDays( Int32 days )
 2887      {
 2888         switch ( this._kind )
 2889         {
 2890            case TSKind.Infinity:
 2891            case TSKind.MinusInfinity:
 2892               return this;
 2893            default:
 2894               return new PgSQLTimestamp( this._date.AddDays( days ), this._time );
 2895         }
 2896      }
 2897
 2898      public PgSQLTimestamp AddYears( Int32 years )
 2899      {
 2900         switch ( this._kind )
 2901         {
 2902            case TSKind.Infinity:
 2903            case TSKind.MinusInfinity:
 2904               return this;
 2905            default:
 2906               return new PgSQLTimestamp( this._date.AddYears( years ), this._time );
 2907         }
 2908      }
 2909
 2910      public PgSQLTimestamp AddMonths( Int32 months )
 2911      {
 2912         switch ( this._kind )
 2913         {
 2914            case TSKind.Infinity:
 2915            case TSKind.MinusInfinity:
 2916               return this;
 2917            default:
 2918               return new PgSQLTimestamp( this._date.AddMonths( months ), this._time );
 2919         }
 2920      }
 2921
 2922      public PgSQLTimestamp Add( PgSQLInterval interval )
 2923      {
 2924         switch ( this._kind )
 2925         {
 2926            case TSKind.Infinity:
 2927            case TSKind.MinusInfinity:
 2928               return this;
 2929            default:
 2930               Int32 overflow;
 2931               var time = this._time.Add( interval, out overflow );
 2932               return new PgSQLTimestamp( this._date.Add( interval, overflow ), time );
 2933         }
 2934      }
 2935
 2936      public PgSQLTimestamp Subtract( PgSQLInterval interval )
 2937      {
 2938         return Add( -interval );
 2939      }
 2940
 2941      public PgSQLInterval Subtract( PgSQLTimestamp timestamp )
 2942      {
 2943         switch ( this._kind )
 2944         {
 2945            case TSKind.Infinity:
 2946            case TSKind.MinusInfinity:
 2947               throw new ArgumentOutOfRangeException( "this", "You cannot subtract infinity timestamps" );
 2948         }
 2949         switch ( timestamp._kind )
 2950         {
 2951            case TSKind.Infinity:
 2952            case TSKind.MinusInfinity:
 2953               throw new ArgumentOutOfRangeException( "timestamp", "You cannot subtract infinity timestamps" );
 2954         }
 2955
 2956         return new PgSQLInterval( 0, this._date.DaysSinceEra - timestamp._date.DaysSinceEra, this._time.Ticks - timesta
 2957      }
 2958
 2959      #endregion
 2960
 2961      #region Normalization
 2962
 2963      public PgSQLTimestamp Normalize()
 2964      {
 2965         return this.Add( PgSQLInterval.Zero );
 2966      }
 2967
 2968      #endregion
 2969
 2970      #region Time zone
 2971
 2972      public PgSQLTimestampTZ AtTimeZone( PgSQLTimeZone timeZoneFrom, PgSQLTimeZone timeZoneTo )
 2973      {
 2974         Int32 overflow;
 2975         var adjusted = new PgSQLTimeTZ( _time, timeZoneFrom ).AtTimeZone( timeZoneTo, out overflow );
 2976         return new PgSQLTimestampTZ( _date.AddDays( overflow ), adjusted );
 2977      }
 2978
 2979      public PgSQLTimestampTZ AtTimeZone( PgSQLTimeZone timeZone )
 2980      {
 2981         return AtTimeZone( timeZone, PgSQLTimeZone.GetLocalTimeZone( _date ) );
 2982      }
 2983
 2984      #endregion
 2985
 2986      #region Comparison
 2987
 2988      public Boolean Equals( PgSQLTimestamp other )
 2989      {
 2990         switch ( this._kind )
 2991         {
 2992            case TSKind.Infinity:
 2993               return other._kind == TSKind.Infinity;
 2994            case TSKind.MinusInfinity:
 2995               return other._kind == TSKind.MinusInfinity;
 2996            default:
 2997               return other._kind == TSKind.Normal && this._date.Equals( other._date ) && this._time.Equals( other._time
 2998         }
 2999      }
 3000
 3001      public override Boolean Equals( Object obj )
 3002      {
 3003         return obj != null && obj is PgSQLTimestamp && this.Equals( (PgSQLTimestamp) obj );
 3004      }
 3005
 3006      public override Int32 GetHashCode()
 3007      {
 3008         switch ( this._kind )
 3009         {
 3010            case TSKind.Infinity:
 3011               return Int32.MaxValue;
 3012            case TSKind.MinusInfinity:
 3013               return Int32.MinValue;
 3014            default:
 3015               // Like in point
 3016               unchecked
 3017               {
 3018                  return ( 17 * 23 + this._date.GetHashCode() ) * 23 + this._time.GetHashCode();
 3019               }
 3020         }
 3021      }
 3022
 3023      Int32 IComparable.CompareTo( Object obj )
 3024      {
 3025         if ( obj == null )
 3026         {
 3027            // This is always 'greater' than null
 3028            return 1;
 3029         }
 3030         else if ( obj is PgSQLTimestamp )
 3031         {
 3032            return this.CompareTo( (PgSQLTimestamp) obj );
 3033         }
 3034         else
 3035         {
 3036            throw new ArgumentException( "Given object must be of type " + this.GetType() + " or null." );
 3037         }
 3038      }
 3039
 3040      public Int32 CompareTo( PgSQLTimestamp other )
 3041      {
 3042         switch ( this._kind )
 3043         {
 3044            case TSKind.Infinity:
 3045               return other._kind == TSKind.Infinity ? 0 : 1;
 3046            case TSKind.MinusInfinity:
 3047               return other._kind == TSKind.MinusInfinity ? 0 : -1;
 3048            default:
 3049               var dateCompare = this._date.CompareTo( other._date );
 3050               return dateCompare == 0 ? this._time.CompareTo( other._time ) : dateCompare;
 3051         }
 3052      }
 3053
 3054      #endregion
 3055
 3056      #region Casts and conversions
 3057
 3058      public static explicit operator PgSQLTimestamp( DateTime x )
 3059      {
 3060         return x == DateTime.MaxValue ?
 3061            Infinity : ( x == DateTime.MinValue ? MinusInfinity :
 3062            new PgSQLTimestamp( new PgSQLDate( x ), new PgSQLTime( x.TimeOfDay ) ) );
 3063      }
 3064
 3065      public static explicit operator DateTime( PgSQLTimestamp x )
 3066      {
 3067         return x.ToDateTime( DateTimeKind.Unspecified );
 3068      }
 3069
 3070      public DateTime ToDateTime( DateTimeKind dtKind )
 3071      {
 3072         switch ( this._kind )
 3073         {
 3074            case TSKind.Infinity:
 3075               return DateTime.MaxValue;
 3076            case TSKind.MinusInfinity:
 3077               return DateTime.MinValue;
 3078            default:
 3079               try
 3080               {
 3081                  return new DateTime( this._date.DaysSinceEra * TimeSpan.TicksPerDay + this._time.Ticks, dtKind );
 3082               }
 3083               catch ( Exception exc )
 3084               {
 3085                  throw new InvalidCastException( "", exc );
 3086               }
 3087         }
 3088      }
 3089
 3090      #endregion
 3091
 3092      #region Operators
 3093
 3094      public static Boolean operator ==( PgSQLTimestamp x, PgSQLTimestamp y )
 3095      {
 3096         return x.Equals( y );
 3097      }
 3098
 3099      public static Boolean operator !=( PgSQLTimestamp x, PgSQLTimestamp y )
 3100      {
 3101         return !( x == y );
 3102      }
 3103
 3104      public static Boolean operator <( PgSQLTimestamp x, PgSQLTimestamp y )
 3105      {
 3106         return x.CompareTo( y ) < 0;
 3107      }
 3108
 3109      public static Boolean operator <=( PgSQLTimestamp x, PgSQLTimestamp y )
 3110      {
 3111         return x.CompareTo( y ) <= 0;
 3112      }
 3113
 3114      public static Boolean operator >( PgSQLTimestamp x, PgSQLTimestamp y )
 3115      {
 3116         return !( x <= y );
 3117      }
 3118
 3119      public static Boolean operator >=( PgSQLTimestamp x, PgSQLTimestamp y )
 3120      {
 3121         return !( x < y );
 3122      }
 3123
 3124      public static PgSQLTimestamp operator +( PgSQLTimestamp timestamp, PgSQLInterval interval )
 3125      {
 3126         return timestamp.Add( interval );
 3127      }
 3128
 3129      public static PgSQLTimestamp operator +( PgSQLInterval interval, PgSQLTimestamp timestamp )
 3130      {
 3131         return timestamp.Add( interval );
 3132      }
 3133
 3134      public static PgSQLTimestamp operator -( PgSQLTimestamp timestamp, PgSQLInterval interval )
 3135      {
 3136         return timestamp.Subtract( interval );
 3137      }
 3138
 3139      public static PgSQLInterval operator -( PgSQLTimestamp x, PgSQLTimestamp y )
 3140      {
 3141         return x.Subtract( y );
 3142      }
 3143
 3144      #endregion
 3145
 3146      #region To and from string
 3147
 3148      private const Byte SEPARATOR = (Byte) ' ';
 3149
 3150      public Int32 GetTextByteCount( IEncodingInfo encoding )
 3151      {
 3152         switch ( this._kind )
 3153         {
 3154            case TSKind.Infinity:
 3155               return encoding.Encoding.GetByteCount( PgSQLDate.INFINITY );
 3156            case TSKind.MinusInfinity:
 3157               return encoding.Encoding.GetByteCount( PgSQLDate.MINUS_INFINITY );
 3158            default:
 3159               return encoding.BytesPerASCIICharacter + this._date.GetTextByteCount( encoding ) + this._time.GetTextByte
 3160         }
 3161      }
 3162
 3163      public void WriteTextBytes( IEncodingInfo encoding, Byte[] array, ref Int32 offset )
 3164      {
 3165         switch ( this._kind )
 3166         {
 3167            case TSKind.Infinity:
 3168               encoding.Encoding.GetBytes( PgSQLDate.INFINITY, 0, PgSQLDate.INFINITY.Length, array, offset );
 3169               break;
 3170            case TSKind.MinusInfinity:
 3171               encoding.Encoding.GetBytes( PgSQLDate.MINUS_INFINITY, 0, PgSQLDate.MINUS_INFINITY.Length, array, offset )
 3172               break;
 3173            default:
 3174               this._date.WriteTextBytes( encoding, array, ref offset );
 3175               encoding.WriteASCIIByte( array, ref offset, SEPARATOR ); // ' '
 3176               this._time.WriteTextBytes( encoding, array, ref offset );
 3177               break;
 3178         }
 3179      }
 3180
 3181      public static PgSQLTimestamp ParseBinaryText( IEncodingInfo encoding, Byte[] array, ref Int32 offset, Int32 count 
 3182      {
 3183         switch ( count )
 3184         {
 3185            case PgSQLDate.INFINITY_CHAR_COUNT:
 3186               return Infinity;
 3187            case PgSQLDate.MINUS_INFINITY_CHAR_COUNT:
 3188               return MinusInfinity;
 3189            default:
 3190               var oldIdx = offset;
 3191               var date = PgSQLDate.ParseBinaryText( encoding, array, ref offset, count );
 3192               encoding.EqualsOrThrow( array, ref offset, SEPARATOR );
 3193               count -= offset - oldIdx;
 3194               return new PgSQLTimestamp(
 3195                  date,
 3196                  PgSQLTime.ParseBinaryText( encoding, array, ref offset, count )
 3197                  );
 3198         }
 3199      }
 3200
 3201      public override String ToString()
 3202      {
 3203         switch ( this._kind )
 3204         {
 3205            case TSKind.Infinity:
 3206               return PgSQLDate.INFINITY;
 3207            case TSKind.MinusInfinity:
 3208               return PgSQLDate.MINUS_INFINITY;
 3209            default:
 3210               return this._date.ToString() + " " + this._time.ToString();
 3211         }
 3212      }
 3213
 3214      public static PgSQLTimestamp Parse( String str )
 3215      {
 3216         PgSQLTimestamp result; Exception error;
 3217         TryParse( str, out result, out error );
 3218         if ( error != null )
 3219         {
 3220            throw error;
 3221         }
 3222         return result;
 3223      }
 3224
 3225      public static Boolean TryParse( String str, out PgSQLTimestamp result )
 3226      {
 3227         Exception error;
 3228         return TryParse( str, out result, out error );
 3229      }
 3230
 3231      private static Boolean TryParse( String str, out PgSQLTimestamp result, out Exception error )
 3232      {
 3233         if ( str == null )
 3234         {
 3235            result = default( PgSQLTimestamp );
 3236            error = new ArgumentNullException( "String" );
 3237         }
 3238         else
 3239         {
 3240            str = str.Trim();
 3241            error = null;
 3242            if ( String.Equals( str, PgSQLDate.INFINITY, StringComparison.OrdinalIgnoreCase ) )
 3243            {
 3244               result = Infinity;
 3245            }
 3246            else if ( String.Equals( str, PgSQLDate.MINUS_INFINITY, StringComparison.OrdinalIgnoreCase ) )
 3247            {
 3248               result = MinusInfinity;
 3249            }
 3250            else
 3251            {
 3252               var idx = str.LastIndexOf( ' ' );
 3253               if ( idx == -1 )
 3254               {
 3255                  error = new FormatException( "Failed to distinguish date and time in timestamp." );
 3256               }
 3257
 3258               PgSQLDate date; PgSQLTime time;
 3259               result = error == null && PgSQLDate.TryParse( str.Substring( 0, idx ), out date, out error ) && PgSQLTime
 3260                  new PgSQLTimestamp( date, time ) :
 3261                  default( PgSQLTimestamp );
 3262            }
 3263         }
 3264         return error == null;
 3265      }
 3266
 3267      #endregion
 3268
 3269   }
 3270
 3271   public struct PgSQLTimestampTZ : IEquatable<PgSQLTimestampTZ>, IComparable, IComparable<PgSQLTimestampTZ>//, IPgSQLDa
 3272   {
 3273      #region Static
 3274
 3275      public static readonly PgSQLTimestampTZ Epoch = new PgSQLTimestampTZ( PgSQLDate.Epoch, PgSQLTimeTZ.AllBalls );
 3276
 3277      public static readonly PgSQLTimestampTZ Era = new PgSQLTimestampTZ( PgSQLDate.Era, PgSQLTimeTZ.AllBalls );
 3278
 3279      public static readonly PgSQLTimestampTZ MinusInfinity = new PgSQLTimestampTZ( PgSQLTimestamp.TSKind.MinusInfinity 
 3280
 3281      public static readonly PgSQLTimestampTZ Infinity = new PgSQLTimestampTZ( PgSQLTimestamp.TSKind.Infinity );
 3282
 3283      public static PgSQLTimestampTZ Now
 3284      {
 3285         get
 3286         {
 3287            return new PgSQLTimestampTZ( PgSQLDate.Now, PgSQLTimeTZ.Now );
 3288         }
 3289      }
 3290
 3291      public static PgSQLTimestampTZ Today
 3292      {
 3293         get
 3294         {
 3295            return new PgSQLTimestampTZ( PgSQLDate.Now );
 3296         }
 3297      }
 3298
 3299      public static PgSQLTimestampTZ Yesterday
 3300      {
 3301         get
 3302         {
 3303            return new PgSQLTimestampTZ( PgSQLDate.Yesterday );
 3304         }
 3305      }
 3306
 3307      public static PgSQLTimestampTZ Tomorrow
 3308      {
 3309         get
 3310         {
 3311            return new PgSQLTimestampTZ( PgSQLDate.Tomorrow );
 3312         }
 3313      }
 3314
 3315      #endregion
 3316
 3317      #region Fields
 3318
 3319      private readonly PgSQLDate _date;
 3320      private readonly PgSQLTimeTZ _time;
 3321      private readonly PgSQLTimestamp.TSKind _kind;
 3322
 3323      #endregion
 3324
 3325      #region Constructors
 3326
 3327      private PgSQLTimestampTZ( PgSQLTimestamp.TSKind kind )
 3328      {
 3329         if ( kind != PgSQLTimestamp.TSKind.Infinity && kind != PgSQLTimestamp.TSKind.MinusInfinity )
 3330         {
 3331            throw new InvalidOperationException( "This constructor may only be used to create infinity or minus infinity
 3332         }
 3333         this._kind = kind;
 3334         this._date = PgSQLDate.Era;
 3335         this._time = PgSQLTimeTZ.AllBalls;
 3336      }
 3337
 3338      public PgSQLTimestampTZ( PgSQLDate date, PgSQLTimeTZ time )
 3339      {
 3340         this._kind = PgSQLTimestamp.TSKind.Normal;
 3341         this._date = date;
 3342         this._time = time;
 3343      }
 3344
 3345      public PgSQLTimestampTZ( PgSQLDate date )
 3346         : this( date, PgSQLTimeTZ.AllBalls )
 3347      {
 3348      }
 3349
 3350      public PgSQLTimestampTZ( Int32 year, Int32 month, Int32 day, Int32 hours, Int32 minutes, Int32 seconds, PgSQLTimeZ
 3351         : this( new PgSQLDate( year, month, day ), new PgSQLTimeTZ( hours, minutes, seconds, timeZone.HasValue ? timeZo
 3352      {
 3353      }
 3354
 3355      public PgSQLTimestampTZ( Int32 year, Int32 month, Int32 day, Int32 hours, Int32 minutes, Decimal seconds, PgSQLTim
 3356         : this( new PgSQLDate( year, month, day ), new PgSQLTimeTZ( hours, minutes, seconds, timeZone.HasValue ? timeZo
 3357      {
 3358      }
 3359
 3360      #endregion
 3361
 3362      #region Properties
 3363
 3364      public Int32 DayOfYear
 3365      {
 3366         get
 3367         {
 3368            return this._date.DayOfYear;
 3369         }
 3370      }
 3371
 3372      public Int32 Year
 3373      {
 3374         get
 3375         {
 3376            return this._date.Year;
 3377         }
 3378      }
 3379
 3380      public Int32 Month
 3381      {
 3382         get
 3383         {
 3384            return this._date.Month;
 3385         }
 3386      }
 3387
 3388      public Int32 Day
 3389      {
 3390         get
 3391         {
 3392            return this._date.Day;
 3393         }
 3394      }
 3395
 3396      public DayOfWeek DayOfWeek
 3397      {
 3398         get
 3399         {
 3400            return this._date.DayOfWeek;
 3401         }
 3402      }
 3403
 3404      public Int32 DaysSinceEra
 3405      {
 3406         get
 3407         {
 3408            return this._date.DaysSinceEra;
 3409         }
 3410      }
 3411
 3412      public Boolean IsLeapYear
 3413      {
 3414         get
 3415         {
 3416            return this._date.IsLeapYear;
 3417         }
 3418      }
 3419
 3420      public PgSQLTime LocalTime
 3421      {
 3422         get
 3423         {
 3424            return this._time.LocalTime;
 3425         }
 3426      }
 3427
 3428      public PgSQLTimeZone TimeZone
 3429      {
 3430         get
 3431         {
 3432            return this._time.TimeZone;
 3433         }
 3434      }
 3435
 3436      public PgSQLTime UTCTime
 3437      {
 3438         get
 3439         {
 3440            return this._time.UTCTime;
 3441         }
 3442      }
 3443
 3444      public Int64 Ticks
 3445      {
 3446         get
 3447         {
 3448            return this._time.Ticks;
 3449         }
 3450      }
 3451
 3452      public Int32 Microseconds
 3453      {
 3454         get
 3455         {
 3456            return this._time.Microseconds;
 3457         }
 3458      }
 3459
 3460      public Int32 Milliseconds
 3461      {
 3462         get
 3463         {
 3464            return this._time.Milliseconds;
 3465         }
 3466      }
 3467
 3468      public Int32 Seconds
 3469      {
 3470         get
 3471         {
 3472            return this._time.Seconds;
 3473         }
 3474      }
 3475
 3476      public Int32 Minutes
 3477      {
 3478         get
 3479         {
 3480            return this._time.Minutes;
 3481         }
 3482      }
 3483
 3484      public Int32 Hours
 3485      {
 3486         get
 3487         {
 3488            return this._time.Hours;
 3489         }
 3490      }
 3491
 3492      public Boolean IsFinite
 3493      {
 3494         get
 3495         {
 3496            return this._kind == PgSQLTimestamp.TSKind.Normal;
 3497         }
 3498      }
 3499
 3500      public Boolean IsInfinity
 3501      {
 3502         get
 3503         {
 3504            return this._kind == PgSQLTimestamp.TSKind.Infinity;
 3505         }
 3506      }
 3507
 3508      public Boolean IsMinusInfinity
 3509      {
 3510         get
 3511         {
 3512            return this._kind == PgSQLTimestamp.TSKind.MinusInfinity;
 3513         }
 3514      }
 3515
 3516      public PgSQLDate Date
 3517      {
 3518         get
 3519         {
 3520            return this._date;
 3521         }
 3522      }
 3523
 3524      public PgSQLTimeTZ Time
 3525      {
 3526         get
 3527         {
 3528            return this._time;
 3529         }
 3530      }
 3531
 3532      #endregion
 3533
 3534      #region Comparison
 3535
 3536      public Boolean Equals( PgSQLTimestampTZ other )
 3537      {
 3538         switch ( this._kind )
 3539         {
 3540            case PgSQLTimestamp.TSKind.Infinity:
 3541               return other._kind == PgSQLTimestamp.TSKind.Infinity;
 3542            case PgSQLTimestamp.TSKind.MinusInfinity:
 3543               return other._kind == PgSQLTimestamp.TSKind.MinusInfinity;
 3544            default:
 3545               return other._kind == PgSQLTimestamp.TSKind.Normal && this._date.Equals( other._date ) && this._time.Equa
 3546         }
 3547      }
 3548
 3549      public override Boolean Equals( Object obj )
 3550      {
 3551         return obj != null && obj is PgSQLTimestampTZ && this.Equals( (PgSQLTimestampTZ) obj );
 3552      }
 3553
 3554      public override Int32 GetHashCode()
 3555      {
 3556         switch ( this._kind )
 3557         {
 3558            case PgSQLTimestamp.TSKind.Infinity:
 3559               return Int32.MaxValue;
 3560            case PgSQLTimestamp.TSKind.MinusInfinity:
 3561               return Int32.MinValue;
 3562            default:
 3563               // Like in point
 3564               unchecked
 3565               {
 3566                  return ( 17 * 23 + this._date.GetHashCode() ) * 23 + this._time.GetHashCode();
 3567               }
 3568         }
 3569      }
 3570
 3571      Int32 IComparable.CompareTo( Object obj )
 3572      {
 3573         if ( obj == null )
 3574         {
 3575            // This is always 'greater' than null
 3576            return 1;
 3577         }
 3578         else if ( obj is PgSQLTimestampTZ )
 3579         {
 3580            return this.CompareTo( (PgSQLTimestampTZ) obj );
 3581         }
 3582         else
 3583         {
 3584            throw new ArgumentException( "Given object must be of type " + this.GetType() + " or null." );
 3585         }
 3586      }
 3587
 3588      public Int32 CompareTo( PgSQLTimestampTZ other )
 3589      {
 3590         switch ( this._kind )
 3591         {
 3592            case PgSQLTimestamp.TSKind.Infinity:
 3593               return other._kind == PgSQLTimestamp.TSKind.Infinity ? 0 : 1;
 3594            case PgSQLTimestamp.TSKind.MinusInfinity:
 3595               return other._kind == PgSQLTimestamp.TSKind.MinusInfinity ? 0 : -1;
 3596            default:
 3597               var dateCompare = this._date.CompareTo( other._date );
 3598               return dateCompare == 0 ? this._time.CompareTo( other._time ) : dateCompare;
 3599         }
 3600      }
 3601
 3602      #endregion
 3603
 3604      #region Arithmetics
 3605
 3606      public PgSQLTimestampTZ AddDays( Int32 days )
 3607      {
 3608         switch ( this._kind )
 3609         {
 3610            case PgSQLTimestamp.TSKind.Infinity:
 3611            case PgSQLTimestamp.TSKind.MinusInfinity:
 3612               return this;
 3613            default:
 3614               return new PgSQLTimestampTZ( this._date.AddDays( days ), this._time );
 3615         }
 3616      }
 3617
 3618      public PgSQLTimestampTZ AddYears( Int32 years )
 3619      {
 3620         switch ( this._kind )
 3621         {
 3622            case PgSQLTimestamp.TSKind.Infinity:
 3623            case PgSQLTimestamp.TSKind.MinusInfinity:
 3624               return this;
 3625            default:
 3626               return new PgSQLTimestampTZ( this._date.AddYears( years ), this._time );
 3627         }
 3628      }
 3629
 3630      public PgSQLTimestampTZ AddMonths( Int32 months )
 3631      {
 3632         switch ( this._kind )
 3633         {
 3634            case PgSQLTimestamp.TSKind.Infinity:
 3635            case PgSQLTimestamp.TSKind.MinusInfinity:
 3636               return this;
 3637            default:
 3638               return new PgSQLTimestampTZ( this._date.AddMonths( months ), this._time );
 3639         }
 3640      }
 3641
 3642      public PgSQLTimestampTZ Add( PgSQLInterval interval )
 3643      {
 3644         switch ( this._kind )
 3645         {
 3646            case PgSQLTimestamp.TSKind.Infinity:
 3647            case PgSQLTimestamp.TSKind.MinusInfinity:
 3648               return this;
 3649            default:
 3650               int overflow;
 3651               var time = this._time.Add( interval, out overflow );
 3652               return new PgSQLTimestampTZ( this._date.Add( interval, overflow ), time );
 3653         }
 3654      }
 3655
 3656      public PgSQLTimestampTZ Subtract( PgSQLInterval interval )
 3657      {
 3658         return Add( -interval );
 3659      }
 3660
 3661      public PgSQLInterval Subtract( PgSQLTimestampTZ timestamp )
 3662      {
 3663         switch ( this._kind )
 3664         {
 3665            case PgSQLTimestamp.TSKind.Infinity:
 3666            case PgSQLTimestamp.TSKind.MinusInfinity:
 3667               throw new ArgumentOutOfRangeException( "this", "You cannot subtract infinity timestamps" );
 3668         }
 3669         switch ( timestamp._kind )
 3670         {
 3671            case PgSQLTimestamp.TSKind.Infinity:
 3672            case PgSQLTimestamp.TSKind.MinusInfinity:
 3673               throw new ArgumentOutOfRangeException( "timestamp", "You cannot subtract infinity timestamps" );
 3674         }
 3675         return new PgSQLInterval( 0, this._date.DaysSinceEra - timestamp._date.DaysSinceEra, ( this._time - timestamp._
 3676      }
 3677
 3678      #endregion
 3679
 3680      #region Normalization
 3681
 3682      public PgSQLTimestampTZ Normalize()
 3683      {
 3684         return this.Add( PgSQLInterval.Zero );
 3685      }
 3686
 3687      #endregion
 3688
 3689      #region Time zone
 3690
 3691      public PgSQLTimestamp AtTimeZone( PgSQLTimeZone timeZone )
 3692      {
 3693         Int32 overflow;
 3694         var adjusted = this._time.AtTimeZone( timeZone, out overflow );
 3695         return new PgSQLTimestamp( _date.AddDays( overflow ), adjusted.LocalTime );
 3696      }
 3697
 3698      #endregion
 3699
 3700      #region Casts
 3701
 3702      public static explicit operator PgSQLTimestampTZ( DateTime x )
 3703      {
 3704         return x == DateTime.MaxValue ?
 3705            Infinity : ( x == DateTime.MinValue ? MinusInfinity :
 3706            new PgSQLTimestampTZ( new PgSQLDate( x ), new PgSQLTimeTZ( x.TimeOfDay, x.Kind == DateTimeKind.Utc ? PgSQLTi
 3707      }
 3708
 3709      public static explicit operator DateTime( PgSQLTimestampTZ x )
 3710      {
 3711         switch ( x._kind )
 3712         {
 3713            case PgSQLTimestamp.TSKind.Infinity:
 3714               return DateTime.MaxValue;
 3715            case PgSQLTimestamp.TSKind.MinusInfinity:
 3716               return DateTime.MinValue;
 3717            default:
 3718               var utc = x.AtTimeZone( PgSQLTimeZone.UTC );
 3719               try
 3720               {
 3721                  return new DateTime( utc.Date.DaysSinceEra * TimeSpan.TicksPerDay + utc.Time.Ticks, DateTimeKind.Utc )
 3722               }
 3723               catch ( Exception exc )
 3724               {
 3725                  throw new InvalidCastException( "", exc );
 3726               }
 3727
 3728         }
 3729      }
 3730
 3731      public static explicit operator PgSQLTimestampTZ( DateTimeOffset x )
 3732      {
 3733         return x == DateTimeOffset.MaxValue ?
 3734            Infinity : ( x == DateTimeOffset.MinValue ? MinusInfinity :
 3735            new PgSQLTimestampTZ( new PgSQLDate( x.Year, x.Month, x.Day ), new PgSQLTimeTZ( x.TimeOfDay, new PgSQLTimeZo
 3736      }
 3737
 3738      public static explicit operator DateTimeOffset( PgSQLTimestampTZ x )
 3739      {
 3740         switch ( x._kind )
 3741         {
 3742            case PgSQLTimestamp.TSKind.Infinity:
 3743               return DateTimeOffset.MaxValue;
 3744            case PgSQLTimestamp.TSKind.MinusInfinity:
 3745               return DateTimeOffset.MinValue;
 3746            default:
 3747               try
 3748               {
 3749                  return new DateTimeOffset( x._date.DaysSinceEra * TimeSpan.TicksPerDay + x._time.Ticks, x.TimeZone );
 3750               }
 3751               catch ( Exception exc )
 3752               {
 3753                  throw new InvalidCastException( "", exc );
 3754               }
 3755
 3756         }
 3757      }
 3758
 3759      #endregion
 3760
 3761      #region Operators
 3762
 3763      public static Boolean operator ==( PgSQLTimestampTZ x, PgSQLTimestampTZ y )
 3764      {
 3765         return x.Equals( y );
 3766      }
 3767
 3768      public static Boolean operator !=( PgSQLTimestampTZ x, PgSQLTimestampTZ y )
 3769      {
 3770         return !( x == y );
 3771      }
 3772
 3773      public static Boolean operator <( PgSQLTimestampTZ x, PgSQLTimestampTZ y )
 3774      {
 3775         return x.CompareTo( y ) < 0;
 3776      }
 3777
 3778      public static Boolean operator <=( PgSQLTimestampTZ x, PgSQLTimestampTZ y )
 3779      {
 3780         return x.CompareTo( y ) <= 0;
 3781      }
 3782
 3783      public static Boolean operator >( PgSQLTimestampTZ x, PgSQLTimestampTZ y )
 3784      {
 3785         return !( x <= y );
 3786      }
 3787
 3788      public static Boolean operator >=( PgSQLTimestampTZ x, PgSQLTimestampTZ y )
 3789      {
 3790         return !( x < y );
 3791      }
 3792
 3793      public static PgSQLTimestampTZ operator +( PgSQLTimestampTZ timestamp, PgSQLInterval interval )
 3794      {
 3795         return timestamp.Add( interval );
 3796      }
 3797
 3798      public static PgSQLTimestampTZ operator +( PgSQLInterval interval, PgSQLTimestampTZ timestamp )
 3799      {
 3800         return timestamp.Add( interval );
 3801      }
 3802
 3803      public static PgSQLTimestampTZ operator -( PgSQLTimestampTZ timestamp, PgSQLInterval interval )
 3804      {
 3805         return timestamp.Subtract( interval );
 3806      }
 3807
 3808      public static PgSQLInterval operator -( PgSQLTimestampTZ x, PgSQLTimestampTZ y )
 3809      {
 3810         return x.Subtract( y );
 3811      }
 3812
 3813      #endregion
 3814
 3815      #region To and from string
 3816
 3817      public Int32 GetTextByteCount( IEncodingInfo encoding )
 3818      {
 3819         switch ( this._kind )
 3820         {
 3821            case PgSQLTimestamp.TSKind.Infinity:
 3822               return encoding.Encoding.GetByteCount( PgSQLDate.INFINITY );
 3823            case PgSQLTimestamp.TSKind.MinusInfinity:
 3824               return encoding.Encoding.GetByteCount( PgSQLDate.MINUS_INFINITY );
 3825            default:
 3826               return encoding.BytesPerASCIICharacter + this._date.GetTextByteCount( encoding ) + this._time.GetTextByte
 3827         }
 3828      }
 3829
 3830      public void WriteTextBytes( IEncodingInfo encoding, Byte[] array, ref Int32 offset )
 3831      {
 3832         switch ( this._kind )
 3833         {
 3834            case PgSQLTimestamp.TSKind.Infinity:
 3835               encoding.Encoding.GetBytes( PgSQLDate.INFINITY, 0, PgSQLDate.INFINITY.Length, array, offset );
 3836               break;
 3837            case PgSQLTimestamp.TSKind.MinusInfinity:
 3838               encoding.Encoding.GetBytes( PgSQLDate.MINUS_INFINITY, 0, PgSQLDate.MINUS_INFINITY.Length, array, offset )
 3839               break;
 3840            default:
 3841               this._date.WriteTextBytes( encoding, array, ref offset );
 3842               encoding.WriteASCIIByte( array, ref offset, 0x020 ); // ' '
 3843               this._time.WriteTextBytes( encoding, array, ref offset );
 3844               break;
 3845         }
 3846      }
 3847
 3848      public static PgSQLTimestampTZ ParseBinaryText( IEncodingInfo encoding, Byte[] array, ref Int32 offset, Int32 coun
 3849      {
 3850         var oldIdx = offset;
 3851         var ts = PgSQLTimestamp.ParseBinaryText( encoding, array, ref offset, count );
 3852         if ( ts.IsInfinity )
 3853         {
 3854            return Infinity;
 3855         }
 3856         else if ( ts.IsMinusInfinity )
 3857         {
 3858            return MinusInfinity;
 3859         }
 3860         else
 3861         {
 3862            count -= offset - oldIdx;
 3863            if ( count <= 0 )
 3864            {
 3865               throw new FormatException( "No room for time zone information" );
 3866            }
 3867            return new PgSQLTimestampTZ( ts.Date, new PgSQLTimeTZ( ts.Time, PgSQLTimeZone.ParseBinaryText( encoding, arr
 3868         }
 3869      }
 3870
 3871      public override String ToString()
 3872      {
 3873         switch ( this._kind )
 3874         {
 3875            case PgSQLTimestamp.TSKind.Infinity:
 3876               return PgSQLDate.INFINITY;
 3877            case PgSQLTimestamp.TSKind.MinusInfinity:
 3878               return PgSQLDate.MINUS_INFINITY;
 3879            default:
 3880               return this._date.ToString() + " " + this._time.ToString();
 3881         }
 3882      }
 3883
 3884      public static PgSQLTimestampTZ Parse( String str )
 3885      {
 3886         TryParse( str, out PgSQLTimestampTZ result, out Exception error );
 3887         if ( error != null )
 3888         {
 3889            throw error;
 3890         }
 3891         return result;
 3892      }
 3893
 3894      public static Boolean TryParse( String str, out PgSQLTimestampTZ result )
 3895      {
 3896         Exception error;
 3897         return TryParse( str, out result, out error );
 3898      }
 3899
 3900      private static Boolean TryParse( String str, out PgSQLTimestampTZ result, out Exception error )
 3901      {
 3902         if ( str == null )
 3903         {
 3904            result = default( PgSQLTimestampTZ );
 3905            error = new ArgumentNullException( "String" );
 3906         }
 3907         else
 3908         {
 3909            str = str.Trim();
 3910            error = null;
 3911            if ( String.Equals( str, PgSQLDate.INFINITY, StringComparison.OrdinalIgnoreCase ) )
 3912            {
 3913               result = Infinity;
 3914            }
 3915            else if ( String.Equals( str, PgSQLDate.MINUS_INFINITY, StringComparison.OrdinalIgnoreCase ) )
 3916            {
 3917               result = MinusInfinity;
 3918            }
 3919            else
 3920            {
 3921               var idx = str.LastIndexOf( ' ' );
 3922               if ( idx == -1 )
 3923               {
 3924                  error = new FormatException( "Failed to distinguish date and time in timestamp." );
 3925               }
 3926
 3927               PgSQLDate date; PgSQLTimeTZ time;
 3928               result = error == null && PgSQLDate.TryParse( str.Substring( 0, idx ), out date, out error ) && PgSQLTime
 3929                  new PgSQLTimestampTZ( date, time ) :
 3930                  default( PgSQLTimestampTZ );
 3931            }
 3932         }
 3933         return error == null;
 3934      }
 3935
 3936      #endregion
 3937
 3938   }
 3939
 3940
 3941}
 3942
 3943#pragma warning restore 1591
 3944
 3945public static partial class E_CBAM
 3946{
 3947
 3948
 3949   internal static IEncodingInfo EqualsOrThrow( this IEncodingInfo encoding, Byte[] array, ref Int32 offset, Byte b )
 3950   {
 53951      if ( array[offset] != b )
 3952      {
 03953         throw new FormatException( "Missing required " + (Char) b + "." );
 3954      }
 53955      offset += encoding.BytesPerASCIICharacter;
 53956      return encoding;
 3957   }
 3958
 3959   internal static Boolean TryParseOptionalNumber(
 3960      this IEncodingInfo encoding,
 3961      Byte[] array,
 3962      ref Int32 offset,
 3963      Byte prefix,
 3964      (Int32 CharCount, Boolean CharCountExactMatch) charCount,
 3965      Int32 maxOffset, // Exclusive
 3966      out Int32 parsedNumber
 3967      )
 3968   {
 13969      var oldIdx = offset;
 13970      var retVal = offset < maxOffset && encoding.ReadASCIIByte( array, ref offset ) == prefix;
 13971      if ( retVal )
 3972      {
 13973         parsedNumber = encoding.ParseInt32Textual( array, ref offset, charCount );
 13974      }
 3975      else
 3976      {
 3977         // 'Reverse back'
 03978         offset = oldIdx;
 03979         parsedNumber = 0;
 3980      }
 3981
 13982      return retVal;
 3983   }
 3984
 3985}