// NpgsqlTypes\DateDatatypes.cs
//
// Author:
//    Jon Hanna. (jon@hackcraft.net)
//
//    Copyright (C) 2007-2008 The Npgsql Development Team
//    npgsql-general@gborg.postgresql.org
//    http://gborg.postgresql.org/project/npgsql/projdisplay.php
//
// Permission to use, copy, modify, and distribute this software and its
// documentation for any purpose, without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph and the following two paragraphs appear in all copies.
//
// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS
// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Npgsql;

//TODO: Lots of convenience methods! There should be nothing you can do with datetime and timestamp that you can't
//do just as well with these - and hence no reason not to choose these if they are appropriate.
//Similarly, lots of documentation is a must.

// Keep the xml comment warning quiet for this file.
#pragma warning disable 1591

namespace NpgsqlTypes
{
    /// <summary>
    /// Represents the PostgreSQL interval datatype.
    /// <remarks>PostgreSQL differs from .NET in how it's interval type doesn't assume 24 hours in a day
    /// (to deal with 23- and 25-hour days caused by daylight savings adjustments) and has a concept
    /// of months that doesn't exist in .NET's <see cref="TimeSpan"/> class. (Neither datatype
    /// has any concessions for leap-seconds).
    /// <para>For most uses just casting to and from TimeSpan will work correctly &#x2014; in particular,
    /// the results of subtracting one <see cref="DateTime"/> or the PostgreSQL date, time and
    /// timestamp types from another should be the same whether you do so in .NET or PostgreSQL &#x2014;
    /// but if the handling of days and months in PostgreSQL is important to your application then you
    /// should use this class instead of <see cref="TimeSpan"/>.</para>
    /// <para>If you don't know whether these differences are important to your application, they
    /// probably arent! Just use <see cref="TimeSpan"/> and do not use this class directly &#x263a;</para>
    /// <para>To avoid forcing unnecessary provider-specific concerns on users who need not be concerned
    /// with them a call to <see cref="System.Data.IDataRecord.GetValue(int)"/> on a field containing an
    /// <see cref="NpgsqlInterval"/> value will return a <see cref="TimeSpan"/> rather than an
    /// <see cref="NpgsqlInterval"/>. If you need the extra functionality of <see cref="NpgsqlInterval"/>
    /// then use <see cref="Npgsql.NpgsqlDataReader.GetInterval(Int32)"/>.</para>
    /// </remarks>
    /// <seealso cref="Ticks"/>
    /// <seealso cref="JustifyDays"/>
    /// <seealso cref="JustifyMonths"/>
    /// <seealso cref="Canonicalize()"/>
    /// </summary>
    [Serializable]
    public struct NpgsqlInterval : IComparable, IComparer, IEquatable<NpgsqlInterval>, IComparable<NpgsqlInterval>,
                                   IComparer<NpgsqlInterval>
    {
        #region Constants

        /// <summary>
        /// Represents the number of ticks (100ns periods) in one microsecond. This field is constant.
        /// </summary>
        public const long TicksPerMicrosecond = TimeSpan.TicksPerMillisecond/1000;

        /// <summary>
        /// Represents the number of ticks (100ns periods) in one millisecond. This field is constant.
        /// </summary>
        public const long TicksPerMillsecond = TimeSpan.TicksPerMillisecond;

        /// <summary>
        /// Represents the number of ticks (100ns periods) in one second. This field is constant.
        /// </summary>
        public const long TicksPerSecond = TimeSpan.TicksPerSecond;

        /// <summary>
        /// Represents the number of ticks (100ns periods) in one minute. This field is constant.
        /// </summary>
        public const long TicksPerMinute = TimeSpan.TicksPerMinute;

        /// <summary>
        /// Represents the number of ticks (100ns periods) in one hour. This field is constant.
        /// </summary>
        public const long TicksPerHour = TimeSpan.TicksPerHour;

        /// <summary>
        /// Represents the number of ticks (100ns periods) in one day. This field is constant.
        /// </summary>
        public const long TicksPerDay = TimeSpan.TicksPerDay;

        /// <summary>
        /// Represents the number of hours in one day (assuming no daylight savings adjustments). This field is constant.
        /// </summary>
        public const int HoursPerDay = 24;

        /// <summary>
        /// Represents the number of days assumed in one month if month justification or unjustifcation is performed.
        /// This is set to 30 for consistency with PostgreSQL. Note that this is means that month adjustments cause
        /// a year to be taken as 30 &#xd7; 12 = 360 rather than 356/366 days.
        /// </summary>
        public const int DaysPerMonth = 30;

        /// <summary>
        /// Represents the number of ticks (100ns periods) in one day, assuming 30 days per month. <seealso cref="DaysPerMonth"/>
        /// </summary>
        public const long TicksPerMonth = TicksPerDay*DaysPerMonth;

        /// <summary>
        /// Represents the number of months in a year. This field is constant.
        /// </summary>
        public const int MonthsPerYear = 12;

        /// <summary>
        /// Represents the maximum <see cref="NpgsqlInterval"/>. This field is read-only.
        /// </summary>
        public static readonly NpgsqlInterval MaxValue = new NpgsqlInterval(long.MaxValue);

        /// <summary>
        /// Represents the minimum <see cref="NpgsqlInterval"/>. This field is read-only.
        /// </summary>
        public static readonly NpgsqlInterval MinValue = new NpgsqlInterval(long.MinValue);

        /// <summary>
        /// Represents the zero <see cref="NpgsqlInterval"/>. This field is read-only.
        /// </summary>
        public static readonly NpgsqlInterval Zero = new NpgsqlInterval(0);

        #endregion

        private readonly int _months;
        private readonly int _days;
        private readonly long _ticks;

        #region Constructors

        /// <summary>
        /// Initializes a new <see cref="NpgsqlInterval"/> to the specified number of ticks.
        /// </summary>
        /// <param name="ticks">A time period expressed in 100ns units.</param>
        public NpgsqlInterval(long ticks)
            : this(new TimeSpan(ticks))
        {
        }

        /// <summary>
        /// Initializes a new <see cref="NpgsqlInterval"/> to hold the same time as a <see cref="TimeSpan"/>
        /// </summary>
        /// <param name="timespan">A time period expressed in a <see cref="TimeSpan"/></param>
        public NpgsqlInterval(TimeSpan timespan)
            : this(0, timespan.Days, timespan.Ticks - (TicksPerDay * timespan.Days))
        {
        }

        /// <summary>
        /// Initializes a new <see cref="NpgsqlInterval"/> to the specified number of months, days
        /// &amp; ticks.
        /// </summary>
        /// <param name="months">Number of months.</param>
        /// <param name="days">Number of days.</param>
        /// <param name="ticks">Number of 100ns units.</param>
        public NpgsqlInterval(int months, int days, long ticks)
        {
            _months = months;
            _days = days;
            _ticks = ticks;
        }

        /// <summary>
        /// Initializes a new <see cref="NpgsqlInterval"/> to the specified number of
        /// days, hours, minutes &amp; seconds.
        /// </summary>
        /// <param name="days">Number of days.</param>
        /// <param name="hours">Number of hours.</param>
        /// <param name="minutes">Number of minutes.</param>
        /// <param name="seconds">Number of seconds.</param>
        public NpgsqlInterval(int days, int hours, int minutes, int seconds)
            : this(0, days, new TimeSpan(hours, minutes, seconds).Ticks)
        {
        }

        /// <summary>
        /// Initializes a new <see cref="NpgsqlInterval"/> to the specified number of
        /// days, hours, minutes, seconds &amp; milliseconds.
        /// </summary>
        /// <param name="days">Number of days.</param>
        /// <param name="hours">Number of hours.</param>
        /// <param name="minutes">Number of minutes.</param>
        /// <param name="seconds">Number of seconds.</param>
        /// <param name="milliseconds">Number of milliseconds.</param>
        public NpgsqlInterval(int days, int hours, int minutes, int seconds, int milliseconds)
            : this(0, days, new TimeSpan(0, hours, minutes, seconds, milliseconds).Ticks)
        {
        }

        /// <summary>
        /// Initializes a new <see cref="NpgsqlInterval"/> to the specified number of
        /// months, days, hours, minutes, seconds &amp; milliseconds.
        /// </summary>
        /// <param name="months">Number of months.</param>
        /// <param name="days">Number of days.</param>
        /// <param name="hours">Number of hours.</param>
        /// <param name="minutes">Number of minutes.</param>
        /// <param name="seconds">Number of seconds.</param>
        /// <param name="milliseconds">Number of milliseconds.</param>
        public NpgsqlInterval(int months, int days, int hours, int minutes, int seconds, int milliseconds)
            : this(months, days, new TimeSpan(0, hours, minutes, seconds, milliseconds).Ticks)
        {
        }

        /// <summary>
        /// Initializes a new <see cref="NpgsqlInterval"/> to the specified number of
        /// years, months, days, hours, minutes, seconds &amp; milliseconds.
        /// <para>Years are calculated exactly equivalent to 12 months.</para>
        /// </summary>
        /// <param name="years">Number of years.</param>
        /// <param name="months">Number of months.</param>
        /// <param name="days">Number of days.</param>
        /// <param name="hours">Number of hours.</param>
        /// <param name="minutes">Number of minutes.</param>
        /// <param name="seconds">Number of seconds.</param>
        /// <param name="milliseconds">Number of milliseconds.</param>
        public NpgsqlInterval(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds)
            : this(years*12 + months, days, new TimeSpan(0, hours, minutes, seconds, milliseconds).Ticks)
        {
        }

        #endregion

        #region Whole Parts

        /// <summary>
        /// The total number of ticks(100ns units) contained. This is the resolution of the
        /// <see cref="NpgsqlInterval"/>  type. This ignores the number of days and
        /// months held. If you want them included use <see cref="UnjustifyInterval()"/> first.
        /// <remarks>The resolution of the PostgreSQL
        /// interval type is by default 1&#xb5;s = 1,000 ns. It may be smaller as follows:
        /// <list type="number">
        /// <item>
        /// <term>interval(0)</term>
        /// <description>resolution of 1s (1 second)</description>
        /// </item>
        /// <item>
        /// <term>interval(1)</term>
        /// <description>resolution of 100ms = 0.1s (100 milliseconds)</description>
        /// </item>
        /// <item>
        /// <term>interval(2)</term>
        /// <description>resolution of 10ms = 0.01s (10 milliseconds)</description>
        /// </item>
        /// <item>
        /// <term>interval(3)</term>
        /// <description>resolution of 1ms = 0.001s (1 millisecond)</description>
        /// </item>
        /// <item>
        /// <term>interval(4)</term>
        /// <description>resolution of 100&#xb5;s = 0.0001s (100 microseconds)</description>
        /// </item>
        /// <item>
        /// <term>interval(5)</term>
        /// <description>resolution of 10&#xb5;s = 0.00001s (10 microseconds)</description>
        /// </item>
        /// <item>
        /// <term>interval(6) or interval</term>
        /// <description>resolution of 1&#xb5;s = 0.000001s (1 microsecond)</description>
        /// </item>
        /// </list>
        /// <para>As such, if the 100-nanosecond resolution is significant to an application, a PostgreSQL interval will
        /// not suffice for those purposes.</para>
        /// <para>In more frequent cases though, the resolution of the interval suffices.
        /// <see cref="NpgsqlInterval"/> will always suffice to handle the resolution of any interval value, and upon
        /// writing to the database, will be rounded to the resolution used.</para>
        /// </remarks>
        /// <returns>The number of ticks in the instance.</returns>
        /// </summary>
        public long Ticks
        {
            get { return _ticks; }
        }

        /// <summary>
        /// Gets the number of whole microseconds held in the instance.
        /// <returns>An  in the range [-999999, 999999].</returns>
        /// </summary>
        public int Microseconds
        {
            get { return (int) ((_ticks/10)%1000000); }
        }

        /// <summary>
        /// Gets the number of whole milliseconds held in the instance.
        /// <returns>An  in the range [-999, 999].</returns>
        /// </summary>
        public int Milliseconds
        {
            get { return (int) ((_ticks/TicksPerMillsecond)%1000); }
        }

        /// <summary>
        /// Gets the number of whole seconds held in the instance.
        /// <returns>An  in the range [-59, 59].</returns>
        /// </summary>
        public int Seconds
        {
            get { return (int) ((_ticks/TicksPerSecond)%60); }
        }

        /// <summary>
        /// Gets the number of whole minutes held in the instance.
        /// <returns>An  in the range [-59, 59].</returns>
        /// </summary>
        public int Minutes
        {
            get { return (int) ((_ticks/TicksPerMinute)%60); }
        }

        /// <summary>
        /// Gets the number of whole hours held in the instance.
        /// <remarks>Note that this can be less than -23 or greater than 23 unless <see cref="JustifyDays()"/>
        /// has been used to produce this instance.</remarks>
        /// </summary>
        public int Hours
        {
            get { return (int) (_ticks/TicksPerHour); }
        }

        /// <summary>
        /// Gets the number of days held in the instance.
        /// <remarks>Note that this does not pay attention to a time component with -24 or less hours or
        /// 24 or more hours, unless <see cref="JustifyDays()"/> has been called to produce this instance.</remarks>
        /// </summary>
        public int Days
        {
            get { return _days; }
        }

        /// <summary>
        /// Gets the number of months held in the instance.
        /// <remarks>Note that this does not pay attention to a day component with -30 or less days or
        /// 30 or more days, unless <see cref="JustifyMonths()"/> has been called to produce this instance.</remarks>
        /// </summary>
        public int Months
        {
            get { return _months; }
        }

        /// <summary>
        /// Returns a <see cref="TimeSpan"/> representing the time component of the instance.
        /// <remarks>Note that this may have a value beyond the range &#xb1;23:59:59.9999999 unless
        /// <see cref="JustifyDays()"/> has been called to produce this instance.</remarks>
        /// </summary>
        public TimeSpan Time
        {
            get { return new TimeSpan(_ticks); }
        }

        #endregion

        #region Total Parts

        /// <summary>
        /// The total number of ticks (100ns units) in the instance, assuming 24 hours in each day and
        /// 30 days in a month.
        /// </summary>
        public long TotalTicks
        {
            get { return Ticks + Days*TicksPerDay + Months*TicksPerMonth; }
        }

        /// <summary>
        /// The total number of microseconds in the instance, assuming 24 hours in each day and
        /// 30 days in a month.
        /// </summary>
        public double TotalMicroseconds
        {
            get { return TotalTicks/10d; }
        }

        /// <summary>
        /// The total number of milliseconds in the instance, assuming 24 hours in each day and
        /// 30 days in a month.
        /// </summary>
        public double TotalMilliseconds
        {
            get { return TotalTicks/(double) TicksPerMillsecond; }
        }

        /// <summary>
        /// The total number of seconds in the instance, assuming 24 hours in each day and
        /// 30 days in a month.
        /// </summary>
        public double TotalSeconds
        {
            get { return TotalTicks/(double) TicksPerSecond; }
        }

        /// <summary>
        /// The total number of minutes in the instance, assuming 24 hours in each day and
        /// 30 days in a month.
        /// </summary>
        public double TotalMinutes
        {
            get { return TotalTicks/(double) TicksPerMinute; }
        }

        /// <summary>
        /// The total number of hours in the instance, assuming 24 hours in each day and
        /// 30 days in a month.
        /// </summary>
        public double TotalHours
        {
            get { return TotalTicks/(double) TicksPerHour; }
        }

        /// <summary>
        /// The total number of days in the instance, assuming 24 hours in each day and
        /// 30 days in a month.
        /// </summary>
        public double TotalDays
        {
            get { return TotalTicks/(double) TicksPerDay; }
        }

        /// <summary>
        /// The total number of months in the instance, assuming 24 hours in each day and
        /// 30 days in a month.
        /// </summary>
        public double TotalMonths
        {
            get { return TotalTicks/(double) TicksPerMonth; }
        }

        #endregion

        #region Create From Part

        /// <summary>
        /// Creates an <see cref="NpgsqlInterval"/> from a number of ticks.
        /// </summary>
        /// <param name="ticks">The number of ticks (100ns units) in the interval.</param>
        /// <returns>A <see cref="Canonicalize()"/>d <see cref="NpgsqlInterval"/> with the given number of ticks.</returns>
        public static NpgsqlInterval FromTicks(long ticks)
        {
            return new NpgsqlInterval(ticks).Canonicalize();
        }

        /// <summary>
        /// Creates an <see cref="NpgsqlInterval"/> from a number of microseconds.
        /// </summary>
        /// <param name="micro">The number of microseconds in the interval.</param>
        /// <returns>A <see cref="Canonicalize()"/>d <see cref="NpgsqlInterval"/> with the given number of microseconds.</returns>
        public static NpgsqlInterval FromMicroseconds(double micro)
        {
            return FromTicks((long) (micro*TicksPerMicrosecond));
        }

        /// <summary>
        /// Creates an <see cref="NpgsqlInterval"/> from a number of milliseconds.
        /// </summary>
        /// <param name="milli">The number of milliseconds in the interval.</param>
        /// <returns>A <see cref="Canonicalize()"/>d <see cref="NpgsqlInterval"/> with the given number of milliseconds.</returns>
        public static NpgsqlInterval FromMilliseconds(double milli)
        {
            return FromTicks((long) (milli*TicksPerMillsecond));
        }

        /// <summary>
        /// Creates an <see cref="NpgsqlInterval"/> from a number of seconds.
        /// </summary>
        /// <param name="seconds">The number of seconds in the interval.</param>
        /// <returns>A <see cref="Canonicalize()"/>d <see cref="NpgsqlInterval"/> with the given number of seconds.</returns>
        public static NpgsqlInterval FromSeconds(double seconds)
        {
            return FromTicks((long) (seconds*TicksPerSecond));
        }

        /// <summary>
        /// Creates an <see cref="NpgsqlInterval"/> from a number of minutes.
        /// </summary>
        /// <param name="minutes">The number of minutes in the interval.</param>
        /// <returns>A <see cref="Canonicalize()"/>d <see cref="NpgsqlInterval"/> with the given number of minutes.</returns>
        public static NpgsqlInterval FromMinutes(double minutes)
        {
            return FromTicks((long) (minutes*TicksPerMinute));
        }

        /// <summary>
        /// Creates an <see cref="NpgsqlInterval"/> from a number of hours.
        /// </summary>
        /// <param name="hours">The number of hours in the interval.</param>
        /// <returns>A <see cref="Canonicalize()"/>d <see cref="NpgsqlInterval"/> with the given number of hours.</returns>
        public static NpgsqlInterval FromHours(double hours)
        {
            return FromTicks((long) (hours*TicksPerHour));
        }

        /// <summary>
        /// Creates an <see cref="NpgsqlInterval"/> from a number of days.
        /// </summary>
        /// <param name="days">The number of days in the interval.</param>
        /// <returns>A <see cref="Canonicalize()"/>d <see cref="NpgsqlInterval"/> with the given number of days.</returns>
        public static NpgsqlInterval FromDays(double days)
        {
            return FromTicks((long) (days*TicksPerDay));
        }

        /// <summary>
        /// Creates an <see cref="NpgsqlInterval"/> from a number of months.
        /// </summary>
        /// <param name="months">The number of months in the interval.</param>
        /// <returns>A <see cref="Canonicalize()"/>d <see cref="NpgsqlInterval"/> with the given number of months.</returns>
        public static NpgsqlInterval FromMonths(double months)
        {
            return FromTicks((long) (months*TicksPerMonth));
        }

        #endregion

        #region Arithmetic

        /// <summary>
        /// Adds another interval to this instance and returns the result.
        /// </summary>
        /// <param name="interval">An <see cref="NpgsqlInterval"/> to add to this instance.</param>
        /// <returns>An <see cref="NpgsqlInterval"></see> whose values are the sums of the two instances.</returns>
        public NpgsqlInterval Add(NpgsqlInterval interval)
        {
            return new NpgsqlInterval(Months + interval.Months, Days + interval.Days, Ticks + interval.Ticks);
        }

        /// <summary>
        /// Subtracts another interval from this instance and returns the result.
        /// </summary>
        /// <param name="interval">An <see cref="NpgsqlInterval"/> to subtract from this instance.</param>
        /// <returns>An <see cref="NpgsqlInterval"></see> whose values are the differences of the two instances.</returns>
        public NpgsqlInterval Subtract(NpgsqlInterval interval)
        {
            return new NpgsqlInterval(Months - interval.Months, Days - interval.Days, Ticks - interval.Ticks);
        }

        /// <summary>
        /// Returns an <see cref="NpgsqlInterval"/> whose value is the negated value of this instance.
        /// </summary>
        /// <returns>An <see cref="NpgsqlInterval"/> whose value is the negated value of this instance.</returns>
        public NpgsqlInterval Negate()
        {
            return new NpgsqlInterval(-Months, -Days, -Ticks);
        }

        /// <summary>
        /// This absolute value of this instance. In the case of some, but not all, components being negative,
        /// the rules used for justification are used to determine if the instance is positive or negative.
        /// </summary>
        /// <returns>An <see cref="NpgsqlInterval"/> whose value is the absolute value of this instance.</returns>
        public NpgsqlInterval Duration()
        {
            return UnjustifyInterval().Ticks < 0 ? Negate() : this;
        }

        #endregion

        #region Justification

        /// <summary>
        /// Equivalent to PostgreSQL's justify_days function.
        /// </summary>
        /// <returns>An <see cref="NpgsqlInterval"/> based on this one, but with any hours outside of the range [-23, 23]
        /// converted into days.</returns>
        public NpgsqlInterval JustifyDays()
        {
            return new NpgsqlInterval(Months, Days + (int) (Ticks/TicksPerDay), Ticks%TicksPerDay);
        }

        /// <summary>
        /// Opposite to PostgreSQL's justify_days function.
        /// </summary>
        /// <returns>An <see cref="NpgsqlInterval"/> based on this one, but with any days converted to multiples of &#xB1;24hours.</returns>
        public NpgsqlInterval UnjustifyDays()
        {
            return new NpgsqlInterval(Months, 0, Ticks + Days*TicksPerDay);
        }

        /// <summary>
        /// Equivalent to PostgreSQL's justify_months function.
        /// </summary>
        /// <returns>An <see cref="NpgsqlInterval"/> based on this one, but with any days outside of the range [-30, 30]
        /// converted into months.</returns>
        public NpgsqlInterval JustifyMonths()
        {
            return new NpgsqlInterval(Months + Days/DaysPerMonth, Days%DaysPerMonth, Ticks);
        }

        /// <summary>
        /// Opposite to PostgreSQL's justify_months function.
        /// </summary>
        /// <returns>An <see cref="NpgsqlInterval"/> based on this one, but with any months converted to multiples of &#xB1;30days.</returns>
        public NpgsqlInterval UnjustifyMonths()
        {
            return new NpgsqlInterval(0, Days + Months*DaysPerMonth, Ticks);
        }

        /// <summary>
        /// Equivalent to PostgreSQL's justify_interval function.
        /// </summary>
        /// <returns>An <see cref="NpgsqlInterval"/> based on this one,
        /// but with any months converted to multiples of &#xB1;30days
        /// and then with any days converted to multiples of &#xB1;24hours</returns>
        public NpgsqlInterval JustifyInterval()
        {
            return JustifyMonths().JustifyDays();
        }

        /// <summary>
        /// Opposite to PostgreSQL's justify_interval function.
        /// </summary>
        /// <returns>An <see cref="NpgsqlInterval"/> based on this one, but with any months converted to multiples of &#xB1;30days and then any days converted to multiples of &#xB1;24hours;</returns>
        public NpgsqlInterval UnjustifyInterval()
        {
            return new NpgsqlInterval(Ticks + Days*TicksPerDay + Months*DaysPerMonth*TicksPerDay);
        }

        /// <summary>
        /// Produces a canonical NpgslInterval with 0 months and hours in the range of [-23, 23].
        /// <remarks>
        /// <para>
        /// While the fact that for many purposes, two different <see cref="NpgsqlInterval"/> instances could be considered
        /// equivalent (e.g. one with 2days, 3hours and one with 1day 27hours) there are different possible canonical forms.
        /// </para><para>
        /// E.g. we could move all excess hours into days and all excess days into months and have the most readable form,
        /// or we could move everything into the ticks and have the form that allows for the easiest arithmetic) the form
        /// chosen has two important properties that make it the best choice.
        /// </para><para>First, it is closest two how
        /// <see cref="TimeSpan"/> objects are most often represented. Second, it is compatible with results of many
        /// PostgreSQL functions, particularly with age() and the results of subtracting one date, time or timestamp from
        /// another.
        /// </para>
        /// <para>Note that the results of casting a <see cref="TimeSpan"/> to <see cref="NpgsqlInterval"/> is
        /// canonicalised.</para>
        /// </remarks>
        /// </summary>
        /// <returns>An <see cref="NpgsqlTypes.NpgsqlInterval"/> based on this one, but with months converted to multiples of &#xB1;30days and with any hours outside of the range [-23, 23]
        /// converted into days.</returns>
        public NpgsqlInterval Canonicalize()
        {
            return new NpgsqlInterval(0, Days + Months*DaysPerMonth + (int) (Ticks/TicksPerDay), Ticks%TicksPerDay);
        }

        #endregion

        #region Casts

        /// <summary>
        /// Implicit cast of a <see cref="TimeSpan"/> to an <see cref="NpgsqlInterval"/>
        /// </summary>
        /// <param name="timespan">A <see cref="TimeSpan"/></param>
        /// <returns>An eqivalent, canonical, <see cref="NpgsqlInterval"/>.</returns>
        public static implicit operator NpgsqlInterval(TimeSpan timespan)
        {
            return new NpgsqlInterval(timespan).Canonicalize();
        }

        /// <summary>
        /// Implicit cast of an <see cref="NpgsqlInterval"/> to a <see cref="TimeSpan"/>.
        /// </summary>
        /// <param name="interval">A <see cref="NpgsqlInterval"/>.</param>
        /// <returns>An equivalent <see cref="TimeSpan"/>.</returns>
        public static explicit operator TimeSpan(NpgsqlInterval interval)
        {
            return new TimeSpan(interval.Ticks + interval.Days*TicksPerDay + interval.Months*DaysPerMonth*TicksPerDay);
        }

        #endregion

        #region Comparison

        /// <summary>
        /// Returns true if another <see cref="NpgsqlInterval"/> is exactly the same as this instance.
        /// </summary>
        /// <param name="other">An <see cref="NpgsqlInterval"/> for comparison.</param>
        /// <returns>true if the two <see cref="NpgsqlInterval"/> instances are exactly the same,
        /// false otherwise.</returns>
        public bool Equals(NpgsqlInterval other)
        {
            return Ticks == other.Ticks && Days == other.Days && Months == other.Months;
        }

        /// <summary>
        /// Returns true if another object is an <see cref="NpgsqlInterval"/>, that is exactly the same as
        /// this instance
        /// </summary>
        /// <param name="obj">An <see cref="Object"/> for comparison.</param>
        /// <returns>true if the argument is an <see cref="NpgsqlInterval"/> and is exactly the same
        /// as this one, false otherwise.</returns>
        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }
            if (obj is NpgsqlInterval)
            {
                return Equals((NpgsqlInterval) obj);
            }
            return false;
        }

        /// <summary>
        /// Compares two <see cref="NpgsqlInterval"/> instances.
        /// </summary>
        /// <param name="x">The first <see cref="NpgsqlInterval"/>.</param>
        /// <param name="y">The second <see cref="NpgsqlInterval"/>.</param>
        /// <returns>0 if the two are equal or equivalent. A value greater than zero if x is greater than y,
        /// a value less than zero if x is less than y.</returns>
        public static int Compare(NpgsqlInterval x, NpgsqlInterval y)
        {
            return x.CompareTo(y);
        }

        int IComparer<NpgsqlInterval>.Compare(NpgsqlInterval x, NpgsqlInterval y)
        {
            return x.CompareTo(y);
        }

        int IComparer.Compare(object x, object y)
        {
            if (x == null)
            {
                return y == null ? 0 : 1;
            }
            if (y == null)
            {
                return -1;
            }
            try
            {
                return ((IComparable) x).CompareTo(y);
            }
            catch (Exception)
            {
                throw new ArgumentException();
            }
        }

        /// <summary>
        /// A hash code suitable for uses with hashing algorithms.
        /// </summary>
        /// <returns>An signed integer.</returns>
        public override int GetHashCode()
        {
            return UnjustifyInterval().Ticks.GetHashCode();
        }

        /// <summary>
        /// Compares this instance with another/
        /// </summary>
        /// <param name="other">An <see cref="NpgsqlInterval"/> to compare this with.</param>
        /// <returns>0 if the instances are equal or equivalent. A value less than zero if
        /// this instance is less than the argument. A value greater than zero if this instance
        /// is greater than the instance.</returns>
        public int CompareTo(NpgsqlInterval other)
        {
            return UnjustifyInterval().Ticks.CompareTo(other.UnjustifyInterval().Ticks);
        }

        /// <summary>
        /// Compares this instance with another/
        /// </summary>
        /// <param name="other">An object to compare this with.</param>
        /// <returns>0 if the argument is an <see cref="NpgsqlInterval"/> and the instances are equal or equivalent.
        /// A value less than zero if the argument is an <see cref="NpgsqlInterval"/> and
        /// this instance is less than the argument.
        /// A value greater than zero if the argument is an <see cref="NpgsqlInterval"/> and this instance
        /// is greater than the instance.</returns>
        /// A value greater than zero if the argument is null.
        /// <exception cref="ArgumentException">The argument is not an <see cref="NpgsqlInterval"/>.</exception>
        public int CompareTo(object other)
        {
            if (other == null)
            {
                return 1;
            }
            else if (other is NpgsqlInterval)
            {
                return CompareTo((NpgsqlInterval) other);
            }
            else
            {
                throw new ArgumentException();
            }
        }

        #endregion

        #region To And From Strings

        /// <summary>
        /// Parses a <see cref="String"/> and returns a <see cref="NpgsqlInterval"/> instance.
        /// Designed to use the formats generally returned by PostgreSQL.
        /// </summary>
        /// <param name="str">The <see cref="String"/> to parse.</param>
        /// <returns>An <see cref="NpgsqlInterval"/> represented by the argument.</returns>
        /// <exception cref="ArgumentNullException">The string was null.</exception>
        /// <exception cref="OverflowException">A value obtained from parsing the string exceeded the values allowed for the relevant component.</exception>
        /// <exception cref="FormatException">The string was not in a format that could be parsed to produce an <see cref="NpgsqlInterval"/>.</exception>
        public static NpgsqlInterval Parse(string str)
        {
            if (str == null)
            {
                throw new ArgumentNullException("str");
            }
            str = str.Replace('s', ' '); //Quick and easy way to catch plurals.
            try
            {
                int years = 0;
                int months = 0;
                int days = 0;
                int hours = 0;
                int minutes = 0;
                decimal seconds = 0m;
                int idx = str.IndexOf("year");
                if (idx > 0)
                {
                    years = int.Parse(str.Substring(0, idx));
                    str = SafeSubstring(str, idx + 5);
                }
                idx = str.IndexOf("mon");
                if (idx > 0)
                {
                    months = int.Parse(str.Substring(0, idx));
                    str = SafeSubstring(str, idx + 4);
                }
                idx = str.IndexOf("day");
                if (idx > 0)
                {
                    days = int.Parse(str.Substring(0, idx));
                    str = SafeSubstring(str, idx + 4).Trim();
                }
                if (str.Length > 0)
                {
                    bool isNegative = str[0] == '-';
                    string[] parts = str.Split(':');
                    switch (parts.Length) //One of those times that fall-through would actually be good.
                    {
                        case 1:
                            hours = int.Parse(parts[0]);
                            break;
                        case 2:
                            hours = int.Parse(parts[0]);
                            minutes = int.Parse(parts[1]);
                            break;
                        default:
                            hours = int.Parse(parts[0]);
                            minutes = int.Parse(parts[1]);
                            seconds = decimal.Parse(parts[2], System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
                            break;
                    }
                    if (isNegative)
                    {
                        minutes *= -1;
                        seconds *= -1;
                    }
                }
                long ticks = hours*TicksPerHour + minutes*TicksPerMinute + (long) (seconds*TicksPerSecond);
                return new NpgsqlInterval(years*MonthsPerYear + months, days, ticks);
            }
            catch (OverflowException)
            {
                throw;
            }
            catch (Exception)
            {
                throw new FormatException();
            }
        }

        private static string SafeSubstring(string s, int startIndex)
        {
            if (startIndex >= s.Length)
                return string.Empty;
            else
                return s.Substring(startIndex);
        }

        /// <summary>
        /// Attempt to parse a <see cref="String"/> to produce an <see cref="NpgsqlInterval"/>.
        /// </summary>
        /// <param name="str">The <see cref="String"/> to parse.</param>
        /// <param name="result">(out) The <see cref="NpgsqlInterval"/> produced, or <see cref="Zero"/> if the parsing failed.</param>
        /// <returns>true if the parsing succeeded, false otherwise.</returns>
        public static bool TryParse(string str, out NpgsqlInterval result)
        {
            try
            {
                result = Parse(str);
                return true;
            }
            catch (Exception)
            {
                result = Zero;
                return false;
            }
        }

        /// <summary>
        /// Create a <see cref="String"/> representation of the <see cref="NpgsqlInterval"/> instance.
        /// The format returned is of the form:
        /// [M mon[s]] [d day[s]] [HH:mm:ss[.f[f[f[f[f[f[f[f[f]]]]]]]]]]
        /// A zero <see cref="NpgsqlInterval"/> is represented as 00:00:00
        /// <remarks>
        /// Ticks are 100ns, Postgress resolution is only to 1&#xb5;s at most. Hence we lose 1 or more decimal
        /// precision in storing values in the database. Despite this, this method will output that extra
        /// digit of precision. It's forward-compatible with any future increases in resolution up to 100ns,
        /// and also makes this ToString() more applicable to any other use-case.
        /// </remarks>
        /// </summary>
        /// <returns>The <see cref="String"/> representation.</returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            if (Months != 0)
            {
                sb.Append(Months).Append(Math.Abs(Months) == 1 ? " mon " : " mons ");
            }
            if (Days != 0)
            {
                if (Months < 0 && Days > 0)
                {
                    sb.Append('+');
                }
                sb.Append(Days).Append(Math.Abs(Days) == 1 ? " day " : " days ");
            }
            if (Ticks != 0 || sb.Length == 0)
            {
                if(Ticks < 0)
                {
                    sb.Append('-');
                }
                else if (Days < 0 || (Days == 0 && Months < 0))
                {
                    sb.Append('+');
                }
                // calculate total seconds and then subtract total whole minutes in seconds to get just the seconds and fractional part
                decimal seconds = _ticks / (decimal)TicksPerSecond - (_ticks / TicksPerMinute) * 60;
                sb.Append(Math.Abs(Hours).ToString("D2")).Append(':').Append(Math.Abs(Minutes).ToString("D2")).Append(':').Append(Math.Abs(seconds).ToString("0#.######", System.Globalization.CultureInfo.InvariantCulture.NumberFormat));

            }
            if (sb[sb.Length - 1] == ' ')
            {
                sb.Remove(sb.Length - 1, 1);
            }
            return sb.ToString();
        }

        #endregion

        #region Common Operators

        /// <summary>
        /// Adds two <see cref="NpgsqlInterval"/> together.
        /// </summary>
        /// <param name="x">The first <see cref="NpgsqlInterval"/> to add.</param>
        /// <param name="y">The second <see cref="NpgsqlInterval"/> to add.</param>
        /// <returns>An <see cref="NpgsqlInterval"/> whose values are the sum of the arguments.</returns>
        public static NpgsqlInterval operator +(NpgsqlInterval x, NpgsqlInterval y)
        {
            return x.Add(y);
        }

        /// <summary>
        /// Subtracts one <see cref="NpgsqlInterval"/> from another.
        /// </summary>
        /// <param name="x">The <see cref="NpgsqlInterval"/> to subtract the other from.</param>
        /// <param name="y">The <see cref="NpgsqlInterval"/> to subtract from the other.</param>
        /// <returns>An <see cref="NpgsqlInterval"/> whose values are the difference of the arguments</returns>
        public static NpgsqlInterval operator -(NpgsqlInterval x, NpgsqlInterval y)
        {
            return x.Subtract(y);
        }

        /// <summary>
        /// Returns true if two <see cref="NpgsqlInterval"/> are exactly the same.
        /// </summary>
        /// <param name="x">The first <see cref="NpgsqlInterval"/> to compare.</param>
        /// <param name="y">The second <see cref="NpgsqlInterval"/> to compare.</param>
        /// <returns>true if the two arguments are exactly the same, false otherwise.</returns>
        public static bool operator ==(NpgsqlInterval x, NpgsqlInterval y)
        {
            return x.Equals(y);
        }

        /// <summary>
        /// Returns false if two <see cref="NpgsqlInterval"/> are exactly the same.
        /// </summary>
        /// <param name="x">The first <see cref="NpgsqlInterval"/> to compare.</param>
        /// <param name="y">The second <see cref="NpgsqlInterval"/> to compare.</param>
        /// <returns>false if the two arguments are exactly the same, true otherwise.</returns>
        public static bool operator !=(NpgsqlInterval x, NpgsqlInterval y)
        {
            return !(x == y);
        }

        /// <summary>
        /// Compares two <see cref="NpgsqlInterval"/> instances to see if the first is less than the second
        /// </summary>
        /// <param name="x">The first <see cref="NpgsqlInterval"/> to compare.</param>
        /// <param name="y">The second <see cref="NpgsqlInterval"/> to compare.</param>
        /// <returns>true if the first <see cref="NpgsqlInterval"/> is less than second, false otherwise.</returns>
        public static bool operator <(NpgsqlInterval x, NpgsqlInterval y)
        {
            return x.UnjustifyInterval().Ticks < y.UnjustifyInterval().Ticks;
        }

        /// <summary>
        /// Compares two <see cref="NpgsqlInterval"/> instances to see if the first is less than or equivalent to the second
        /// </summary>
        /// <param name="x">The first <see cref="NpgsqlInterval"/> to compare.</param>
        /// <param name="y">The second <see cref="NpgsqlInterval"/> to compare.</param>
        /// <returns>true if the first <see cref="NpgsqlInterval"/> is less than or equivalent to second, false otherwise.</returns>
        public static bool operator <=(NpgsqlInterval x, NpgsqlInterval y)
        {
            return x.UnjustifyInterval().Ticks <= y.UnjustifyInterval().Ticks;
        }

        /// <summary>
        /// Compares two <see cref="NpgsqlInterval"/> instances to see if the first is greater than the second
        /// </summary>
        /// <param name="x">The first <see cref="NpgsqlInterval"/> to compare.</param>
        /// <param name="y">The second <see cref="NpgsqlInterval"/> to compare.</param>
        /// <returns>true if the first <see cref="NpgsqlInterval"/> is greater than second, false otherwise.</returns>
        public static bool operator >(NpgsqlInterval x, NpgsqlInterval y)
        {
            return !(x <= y);
        }

        /// <summary>
        /// Compares two <see cref="NpgsqlInterval"/> instances to see if the first is greater than or equivalent the second
        /// </summary>
        /// <param name="x">The first <see cref="NpgsqlInterval"/> to compare.</param>
        /// <param name="y">The second <see cref="NpgsqlInterval"/> to compare.</param>
        /// <returns>true if the first <see cref="NpgsqlInterval"/> is greater than or equivalent to the second, false otherwise.</returns>
        public static bool operator >=(NpgsqlInterval x, NpgsqlInterval y)
        {
            return !(x < y);
        }

        /// <summary>
        /// Returns the instance.
        /// </summary>
        /// <param name="x">An <see cref="NpgsqlInterval"/>.</param>
        /// <returns>The argument.</returns>
        public static NpgsqlInterval operator +(NpgsqlInterval x)
        {
            return x;
        }

        /// <summary>
        /// Negates an <see cref="NpgsqlInterval"/> instance.
        /// </summary>
        /// <param name="x">An <see cref="NpgsqlInterval"/>.</param>
        /// <returns>The negation of the argument.</returns>
        public static NpgsqlInterval operator -(NpgsqlInterval x)
        {
            return x.Negate();
        }

        #endregion
    }

    [Serializable]
    public struct NpgsqlDate : IEquatable<NpgsqlDate>, IComparable<NpgsqlDate>, IComparable, IComparer<NpgsqlDate>,
                               IComparer
    {
        private static readonly int[] CommonYearDays = new int[] {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
        private static readonly int[] LeapYearDays = new int[] {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366};
        private static readonly int[] CommonYearMaxes = new int[] {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        private static readonly int[] LeapYearMaxes = new int[] {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        public const int MaxYear = 5874897;
        public const int MinYear = -4714;
        public static readonly NpgsqlDate Epoch = new NpgsqlDate(1970, 1, 1);
        public static readonly NpgsqlDate MaxCalculableValue = new NpgsqlDate(MaxYear, 12, 31);
        public static readonly NpgsqlDate MinCalculableValue = new NpgsqlDate(MinYear, 11, 24);
        public static readonly NpgsqlDate Era = new NpgsqlDate(0);

        public static NpgsqlDate Now
        {
            get { return new NpgsqlDate(DateTime.Now); }
        }

        public static NpgsqlDate Today
        {
            get { return Now; }
        }

        public static NpgsqlDate Yesterday
        {
            get { return Now.AddDays(-1); }
        }

        public static NpgsqlDate Tomorrow
        {
            get { return Now.AddDays(1); }
        }

        public static NpgsqlDate Parse(string str)
        {

            if (str == null)
            {
                throw new ArgumentNullException("str");
            }

            // Handle -infinity and infinity special values.

            if (str == "-infinity")
                return new NpgsqlDate(DateTime.MinValue);

            if (str == "infinity")
                return new NpgsqlDate(DateTime.MaxValue);

            str = str.Trim();
            try
            {
                int idx = str.IndexOf('-');
                if (idx == -1)
                {
                    throw new FormatException();
                }
                int year = int.Parse(str.Substring(0, idx));
                int idxLast = idx + 1;
                if ((idx = str.IndexOf('-', idxLast)) == -1)
                {
                    throw new FormatException();
                }
                int month = int.Parse(str.Substring(idxLast, idx - idxLast));
                idxLast = idx + 1;
                if ((idx = str.IndexOf(' ', idxLast)) == -1)
                {
                    idx = str.Length;
                }
                int day = int.Parse(str.Substring(idxLast, idx - idxLast));
                if (str.Contains("BC"))
                {
                    year = -year;
                }
                return new NpgsqlDate(year, month, day);
            }
            catch (OverflowException)
            {
                throw;
            }
            catch (Exception)
            {
                throw new FormatException();
            }
        }

        public static bool TryParse(string str, out NpgsqlDate date)
        {
            try
            {
                date = Parse(str);
                return true;
            }
            catch
            {
                date = Era;
                return false;
            }
        }

        //Number of days since January 1st CE (January 1st EV). 1 Jan 1 CE = 0, 2 Jan 1 CE = 1, 31 Dec 1 BCE = -1, etc.
        private readonly int _daysSinceEra;

        public NpgsqlDate(int days)
        {
            _daysSinceEra = days;
        }

        public NpgsqlDate(DateTime dateTime)
            : this((int) (dateTime.Ticks/TimeSpan.TicksPerDay))
        {
        }

        public NpgsqlDate(NpgsqlDate copyFrom)
            : this(copyFrom._daysSinceEra)
        {
        }

        public NpgsqlDate(int year, int month, int day)
        {
            if (year == 0 || year < MinYear || year > MaxYear || month < 1 || month > 12 || day < 1 ||
                (day > (IsLeap(year) ? 366 : 365)))
            {
                throw new ArgumentOutOfRangeException();
            }

            _daysSinceEra = DaysForYears(year) + (IsLeap(year) ? LeapYearDays : CommonYearDays)[month - 1] + day - 1;
        }

        private const int DaysInYear = 365; //Common years
        private const int DaysIn4Years = 4*DaysInYear + 1; //Leap year every 4 years.
        private const int DaysInCentury = 25*DaysIn4Years - 1; //Except no leap year every 100.
        private const int DaysIn4Centuries = 4*DaysInCentury + 1; //Except leap year every 400.

        private static int DaysForYears(int years)
        {
            //Number of years after 1CE (0 for 1CE, -1 for 1BCE, 1 for 2CE).
            int calcYear = years < 1 ? years : years - 1;

            return calcYear/400*DaysIn4Centuries //Blocks of 400 years with their leap and common years
                   + calcYear%400/100*DaysInCentury //Remaining blocks of 100 years with their leap and common years
                   + calcYear%100/4*DaysIn4Years //Remaining blocks of 4 years with their leap and common years
                   + calcYear%4*DaysInYear //Remaining years, all common
                   + (calcYear < 0 ? -1 : 0); //And 1BCE is leap.
        }

        public int DayOfYear
        {
            get { return _daysSinceEra - DaysForYears(Year) + 1; }
        }

        public int Year
        {
            get
            {
                int guess = (int) Math.Round(_daysSinceEra/365.2425);
                int test = guess - 1;
                while (DaysForYears(++test) <= _daysSinceEra)
                {
                    ;
                }
                return test - 1;
            }
        }

        public int Month
        {
            get
            {
                int i = 1;
                int target = DayOfYear;
                int[] array = IsLeapYear ? LeapYearDays : CommonYearDays;
                while (target > array[i])
                {
                    ++i;
                }
                return i;
            }
        }

        public int Day
        {
            get { return DayOfYear - (IsLeapYear ? LeapYearDays : CommonYearDays)[Month - 1]; }
        }

        public DayOfWeek DayOfWeek
        {
            get { return (DayOfWeek) ((_daysSinceEra + 1)%7); }
        }

        internal int DaysSinceEra
        {
            get { return _daysSinceEra; }
        }

        public bool IsLeapYear
        {
            get { return IsLeap(Year); }
        }

        private static bool IsLeap(int year)
        {
            //Every 4 years is a leap year
            //Except every 100 years isn't a leap year.
            //Except every 400 years is.
            if (year < 1)
            {
                year = year + 1;
            }
            return (year%4 == 0) && ((year%100 != 0) || (year%400 == 0));
        }

        public NpgsqlDate AddDays(int days)
        {
            return new NpgsqlDate(_daysSinceEra + days);
        }

        public NpgsqlDate AddYears(int years)
        {
            int newYear = Year + years;
            if (newYear >= 0 && _daysSinceEra < 0) //cross 1CE/1BCE divide going up
            {
                ++newYear;
            }
            else if (newYear <= 0 && _daysSinceEra >= 0) //cross 1CE/1BCE divide going down
            {
                --newYear;
            }
            return new NpgsqlDate(newYear, Month, Day);
        }

        public NpgsqlDate AddMonths(int months)
        {
            int newYear = Year;
            int newMonth = Month + months;

            while (newMonth > 12)
            {
                newMonth -= 12;
                newYear += 1;
            };
            while (newMonth < 1)
            {
                newMonth += 12;
                newYear -= 1;
            };
            int maxDay = (IsLeap(newYear) ? LeapYearMaxes : CommonYearMaxes)[newMonth - 1];
            int newDay = Day > maxDay ? maxDay : Day;
            return new NpgsqlDate(newYear, newMonth, newDay);

        }

        public NpgsqlDate Add(NpgsqlInterval interval)
        {
            return AddMonths(interval.Months).AddDays(interval.Days);
        }

        internal NpgsqlDate Add(NpgsqlInterval interval, int carriedOverflow)
        {
            return AddMonths(interval.Months).AddDays(interval.Days + carriedOverflow);
        }

        public int Compare(NpgsqlDate x, NpgsqlDate y)
        {
            return x.CompareTo(y);
        }

        public int Compare(object x, object y)
        {
            if (x == null)
            {
                return y == null ? 0 : -1;
            }
            if (y == null)
            {
                return 1;
            }
            if (!(x is IComparable) || !(y is IComparable))
            {
                throw new ArgumentException();
            }
            return ((IComparable) x).CompareTo(y);
        }

        public bool Equals(NpgsqlDate other)
        {
            return _daysSinceEra == other._daysSinceEra;
        }

        public override bool Equals(object obj)
        {
            return obj != null && obj is NpgsqlDate && Equals((NpgsqlDate) obj);
        }

        public int CompareTo(NpgsqlDate other)
        {
            return _daysSinceEra.CompareTo(other._daysSinceEra);
        }

        public int CompareTo(object obj)
        {
            if (obj == null)
            {
                return 1;
            }
            if (obj is NpgsqlDate)
            {
                return CompareTo((NpgsqlDate) obj);
            }
            throw new ArgumentException();
        }

        public override int GetHashCode()
        {
            return _daysSinceEra;
        }

        public override string ToString()
        {
            //Format of yyyy-MM-dd with " BC" for BCE and optional " AD" for CE which we omit here.
            return
                new StringBuilder(Math.Abs(Year).ToString("D4")).Append('-').Append(Month.ToString("D2")).Append('-').Append(
                    Day.ToString("D2")).Append(_daysSinceEra < 0 ? " BC" : "").ToString();
        }

        public static bool operator ==(NpgsqlDate x, NpgsqlDate y)
        {
            return x.Equals(y);
        }

        public static bool operator !=(NpgsqlDate x, NpgsqlDate y)
        {
            return !(x == y);
        }

        public static bool operator <(NpgsqlDate x, NpgsqlDate y)
        {
            return x._daysSinceEra < y._daysSinceEra;
        }

        public static bool operator >(NpgsqlDate x, NpgsqlDate y)
        {
            return x._daysSinceEra > y._daysSinceEra;
        }

        public static bool operator <=(NpgsqlDate x, NpgsqlDate y)
        {
            return x._daysSinceEra <= y._daysSinceEra;
        }

        public static bool operator >=(NpgsqlDate x, NpgsqlDate y)
        {
            return x._daysSinceEra >= y._daysSinceEra;
        }

        public static explicit operator DateTime(NpgsqlDate date)
        {
            try
            {
                return new DateTime(date._daysSinceEra*NpgsqlInterval.TicksPerDay);
            }
            catch
            {
                throw new InvalidCastException();
            }
        }

        public static explicit operator NpgsqlDate(DateTime date)
        {
            return new NpgsqlDate((int) (date.Ticks/NpgsqlInterval.TicksPerDay));
        }

        public static NpgsqlDate operator +(NpgsqlDate date, NpgsqlInterval interval)
        {
            return date.Add(interval);
        }

        public static NpgsqlDate operator +(NpgsqlInterval interval, NpgsqlDate date)
        {
            return date.Add(interval);
        }

        public static NpgsqlDate operator -(NpgsqlDate date, NpgsqlInterval interval)
        {
            return date.Add(-interval);
        }

        public static NpgsqlInterval operator -(NpgsqlDate dateX, NpgsqlDate dateY)
        {
            return new NpgsqlInterval(0, dateX._daysSinceEra - dateY._daysSinceEra, 0);
        }
    }

    [Serializable]
    public struct NpgsqlTimeZone : IEquatable<NpgsqlTimeZone>, IComparable<NpgsqlTimeZone>, IComparable
    {
        public static NpgsqlTimeZone UTC = new NpgsqlTimeZone(0);
        private readonly int _totalSeconds;

        public NpgsqlTimeZone(TimeSpan ts)
            : this(ts.Ticks)
        {
        }

        private NpgsqlTimeZone(long ticks)
        {
            _totalSeconds = (int) (ticks/NpgsqlInterval.TicksPerSecond);
        }

        public NpgsqlTimeZone(NpgsqlInterval ni)
            : this(ni.Ticks)
        {
        }

        public NpgsqlTimeZone(NpgsqlTimeZone copyFrom)
        {
            _totalSeconds = copyFrom._totalSeconds;
        }

        public NpgsqlTimeZone(int hours, int minutes)
            : this(hours, minutes, 0)
        {
        }

        public NpgsqlTimeZone(int hours, int minutes, int seconds)
        {
            _totalSeconds = hours*60*60 + minutes*60 + seconds;
        }

        public static implicit operator NpgsqlTimeZone(NpgsqlInterval interval)
        {
            return new NpgsqlTimeZone(interval);
        }

        public static implicit operator NpgsqlInterval(NpgsqlTimeZone timeZone)
        {
            return new NpgsqlInterval(timeZone._totalSeconds*NpgsqlInterval.TicksPerSecond);
        }

        public static implicit operator NpgsqlTimeZone(TimeSpan interval)
        {
            return new NpgsqlTimeZone(interval);
        }

        public static implicit operator TimeSpan(NpgsqlTimeZone timeZone)
        {
            return new TimeSpan(timeZone._totalSeconds*NpgsqlInterval.TicksPerSecond);
        }

        public static NpgsqlTimeZone SolarTimeZone(decimal longitude)
        {
            return new NpgsqlTimeZone((long) (longitude/15m*NpgsqlInterval.TicksPerHour));
        }

        public int Hours
        {
            get { return _totalSeconds/60/60; }
        }

        public int Minutes
        {
            get { return (_totalSeconds/60)%60; }
        }

        public int Seconds
        {
            get { return _totalSeconds%60; }
        }

        public static NpgsqlTimeZone CurrentTimeZone
        {
            get { return new NpgsqlTimeZone(TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now)); }
        }

        public static NpgsqlTimeZone LocalTimeZone(NpgsqlDate date)
        {
            DateTime dt;
            if (date.Year >= 1902 && date.Year <= 2038)
            {
                dt = (DateTime) date;
            }
            else
            {
                dt = new DateTime(2000, date.Month, date.Day);
            }
            return new NpgsqlTimeZone(TimeZone.CurrentTimeZone.GetUtcOffset(dt));
        }

        public bool Equals(NpgsqlTimeZone other)
        {
            return _totalSeconds == other._totalSeconds;
        }

        public override bool Equals(object obj)
        {
            return obj != null && obj is NpgsqlTimeZone && Equals((NpgsqlTimeZone) obj);
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder(_totalSeconds < 0 ? "-" : "+").Append(Math.Abs(Hours).ToString("D2"));
            if (Minutes != 0 || Seconds != 0)
            {
                sb.Append(':').Append(Math.Abs(Minutes).ToString("D2"));
                if (Seconds != 0)
                {
                    sb.Append(":").Append(Math.Abs(Seconds).ToString("D2"));
                }
            }
            return sb.ToString();
        }

        public static NpgsqlTimeZone Parse(string str)
        {
            if (str == null)
            {
                throw new ArgumentNullException();
            }
            try
            {
                str = str.Trim();
                bool neg;
                switch (str[0])
                {
                    case '+':
                        neg = false;
                        break;
                    case '-':
                        neg = true;
                        break;
                    default:
                        throw new FormatException();
                }
                int hours;
                int minutes;
                int seconds;
                string[] parts = str.Substring(1).Split(':');
                switch (parts.Length) //One of those times that fall-through would actually be good.
                {
                    case 1:
                        hours = int.Parse(parts[0]);
                        minutes = seconds = 0;
                        break;
                    case 2:
                        hours = int.Parse(parts[0]);
                        minutes = int.Parse(parts[1]);
                        seconds = 0;
                        break;
                    default:
                        hours = int.Parse(parts[0]);
                        minutes = int.Parse(parts[1]);
                        seconds = int.Parse(parts[2]);
                        break;
                }
                int totalSeconds = (hours*60*60 + minutes*60 + seconds)*(neg ? -1 : 1);
                return new NpgsqlTimeZone(totalSeconds*NpgsqlInterval.TicksPerSecond);
            }
            catch (OverflowException)
            {
                throw;
            }
            catch
            {
                throw new FormatException();
            }
        }

        public static bool TryParse(string str, NpgsqlTimeZone tz)
        {
            try
            {
                tz = Parse(str);
                return true;
            }
            catch
            {
                tz = UTC;
                return false;
            }
        }

        public override int GetHashCode()
        {
            return _totalSeconds;
        }

        //Note, +01:00 is less than -01:00
        public int CompareTo(NpgsqlTimeZone other)
        {
            return -(_totalSeconds.CompareTo(other._totalSeconds));
        }

        public int CompareTo(object obj)
        {
            if (obj == null)
            {
                return 1;
            }
            if (obj is NpgsqlTimeZone)
            {
                return CompareTo((NpgsqlTimeZone) obj);
            }
            throw new ArgumentException();
        }

        public static NpgsqlTimeZone operator -(NpgsqlTimeZone tz)
        {
            return new NpgsqlTimeZone(-tz._totalSeconds);
        }

        public static NpgsqlTimeZone operator +(NpgsqlTimeZone tz)
        {
            return tz;
        }

        public static bool operator ==(NpgsqlTimeZone x, NpgsqlTimeZone y)
        {
            return x.Equals(y);
        }

        public static bool operator !=(NpgsqlTimeZone x, NpgsqlTimeZone y)
        {
            return !(x == y);
        }

        public static bool operator <(NpgsqlTimeZone x, NpgsqlTimeZone y)
        {
            return x.CompareTo(y) < 0;
        }

        public static bool operator <=(NpgsqlTimeZone x, NpgsqlTimeZone y)
        {
            return x.CompareTo(y) <= 0;
        }

        public static bool operator >(NpgsqlTimeZone x, NpgsqlTimeZone y)
        {
            return x.CompareTo(y) > 0;
        }

        public static bool operator >=(NpgsqlTimeZone x, NpgsqlTimeZone y)
        {
            return x.CompareTo(y) >= 0;
        }
    }

    [Serializable]
    public struct NpgsqlTime : IEquatable<NpgsqlTime>, IComparable<NpgsqlTime>, IComparable, IComparer<NpgsqlTime>,
                               IComparer
    {
        public static readonly NpgsqlTime AllBalls = new NpgsqlTime(0);

        public static NpgsqlTime Now
        {
            get { return new NpgsqlTime(DateTime.Now.TimeOfDay); }
        }

        private readonly long _ticks;

        public NpgsqlTime(long ticks)
        {
            if (ticks == NpgsqlInterval.TicksPerDay)
            {
                _ticks = ticks;
            }
            else
            {
                ticks %= NpgsqlInterval.TicksPerDay;
                _ticks = ticks < 0 ? ticks + NpgsqlInterval.TicksPerDay : ticks;
            }
        }

        public NpgsqlTime(TimeSpan time)
            : this(time.Ticks)
        {
        }

        public NpgsqlTime(NpgsqlInterval time)
            : this(time.Ticks)
        {
        }

        public NpgsqlTime(NpgsqlTime copyFrom)
            : this(copyFrom.Ticks)
        {
        }

        public NpgsqlTime(int hours, int minutes, int seconds)
            : this(hours, minutes, seconds, 0)
        {
        }

        public NpgsqlTime(int hours, int minutes, int seconds, int microseconds)
            : this(
                hours*NpgsqlInterval.TicksPerHour + minutes*NpgsqlInterval.TicksPerMinute + seconds*NpgsqlInterval.TicksPerSecond +
                microseconds*NpgsqlInterval.TicksPerMicrosecond)
        {
        }

        public NpgsqlTime(int hours, int minutes, decimal seconds)
            : this(
                hours*NpgsqlInterval.TicksPerHour + minutes*NpgsqlInterval.TicksPerMinute +
                (long) (seconds*NpgsqlInterval.TicksPerSecond))
        {
        }

        public NpgsqlTime(int hours, int minutes, double seconds)
            : this(hours, minutes, (decimal) seconds)
        {
        }

        /// <summary>
        /// The total number of ticks(100ns units) contained. This is the resolution of the
        /// <see cref="NpgsqlTime"/>  type.
        /// <remarks>The resolution of the PostgreSQL
        /// interval type is by default 1&#xb5;s = 1,000 ns. It may be smaller as follows:
        /// <list type="number">
        /// <item>
        /// <term>time(0)</term>
        /// <description>resolution of 1s (1 second)</description>
        /// </item>
        /// <item>
        /// <term>time(1)</term>
        /// <description>resolution of 100ms = 0.1s (100 milliseconds)</description>
        /// </item>
        /// <item>
        /// <term>time(2)</term>
        /// <description>resolution of 10ms = 0.01s (10 milliseconds)</description>
        /// </item>
        /// <item>
        /// <term>time(3)</term>
        /// <description>resolution of 1ms = 0.001s (1 millisecond)</description>
        /// </item>
        /// <item>
        /// <term>time(4)</term>
        /// <description>resolution of 100&#xb5;s = 0.0001s (100 microseconds)</description>
        /// </item>
        /// <item>
        /// <term>time(5)</term>
        /// <description>resolution of 10&#xb5;s = 0.00001s (10 microseconds)</description>
        /// </item>
        /// <item>
        /// <term>time(6) or interval</term>
        /// <description>resolution of 1&#xb5;s = 0.000001s (1 microsecond)</description>
        /// </item>
        /// </list>
        /// <para>As such, if the 100-nanosecond resolution is significant to an application, a PostgreSQL time will
        /// not suffice for those purposes.</para>
        /// <para>In more frequent cases though, the resolution of time suffices.
        /// <see cref="NpgsqlTime"/> will always suffice to handle the resolution of any time value, and upon
        /// writing to the database, will be rounded to the resolution used.</para>
        /// </remarks>
        /// <returns>The number of ticks in the instance.</returns>
        /// </summary>
        public long Ticks
        {
            get { return _ticks; }
        }

        /// <summary>
        /// Gets the number of whole microseconds held in the instance.
        /// <returns>An integer in the range [0, 999999].</returns>
        /// </summary>
        public int Microseconds
        {
            get { return (int) ((_ticks/10)%1000000); }
        }

        /// <summary>
        /// Gets the number of whole milliseconds held in the instance.
        /// <returns>An integer in the range [0, 999].</returns>
        /// </summary>
        public int Milliseconds
        {
            get { return (int) ((_ticks/NpgsqlInterval.TicksPerMillsecond)%1000); }
        }

        /// <summary>
        /// Gets the number of whole seconds held in the instance.
        /// <returns>An interger in the range [0, 59].</returns>
        /// </summary>
        public int Seconds
        {
            get { return (int) ((_ticks/NpgsqlInterval.TicksPerSecond)%60); }
        }

        /// <summary>
        /// Gets the number of whole minutes held in the instance.
        /// <returns>An integer in the range [0, 59].</returns>
        /// </summary>
        public int Minutes
        {
            get { return (int) ((_ticks/NpgsqlInterval.TicksPerMinute)%60); }
        }

        /// <summary>
        /// Gets the number of whole hours held in the instance.
        /// <remarks>Note that the time 24:00:00 can be stored for roundtrip compatibility. Any calculations on such a
        /// value will normalised it to 00:00:00.</remarks>
        /// </summary>
        public int Hours
        {
            get { return (int) (_ticks/NpgsqlInterval.TicksPerHour); }
        }

        /// <summary>
        /// Normalise this time; if it is 24:00:00, convert it to 00:00:00
        /// </summary>
        /// <returns>This time, normalised</returns>
        public NpgsqlTime Normalize()
        {
            return new NpgsqlTime(_ticks%NpgsqlInterval.TicksPerDay);
        }

        public bool Equals(NpgsqlTime other)
        {
            return Ticks == other.Ticks;
        }

        public override bool Equals(object obj)
        {
            return obj != null && obj is NpgsqlTime && Equals((NpgsqlTime) obj);
        }

        public override int GetHashCode()
        {
            return Ticks.GetHashCode();
        }

        public override string ToString()
        {
            // calculate total seconds and then subtract total whole minutes in seconds to get just the seconds and fractional part
            decimal seconds = _ticks / (decimal)NpgsqlInterval.TicksPerSecond - (_ticks / NpgsqlInterval.TicksPerMinute) * 60;
            StringBuilder sb =
                new StringBuilder(Hours.ToString("D2")).Append(':').Append(Minutes.ToString("D2")).Append(':').Append(
                    seconds.ToString("0#.######", System.Globalization.CultureInfo.InvariantCulture.NumberFormat));
            return sb.ToString();
        }

        public static NpgsqlTime Parse(string str)
        {
            if (str == null)
            {
                throw new ArgumentNullException();
            }
            try
            {
                int hours = 0;
                int minutes = 0;
                decimal seconds = 0m;
                string[] parts = str.Split(':');
                switch (parts.Length) //One of those times that fall-through would actually be good.
                {
                    case 1:
                        hours = int.Parse(parts[0]);
                        break;
                    case 2:
                        hours = int.Parse(parts[0]);
                        minutes = int.Parse(parts[1]);
                        break;
                    default:
                        hours = int.Parse(parts[0]);
                        minutes = int.Parse(parts[1]);
                        seconds = decimal.Parse(parts[2], System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
                        break;
                }
                if (hours < 0 || hours > 24 || minutes < 0 || minutes > 59 || seconds < 0m || seconds >= 60 ||
                    (hours == 24 && (minutes != 0 || seconds != 0m)))
                {
                    throw new OverflowException();
                }
                return new NpgsqlTime(hours, minutes, seconds);
            }
            catch (OverflowException)
            {
                throw;
            }
            catch
            {
                throw new FormatException();
            }
        }

        public static bool TryParse(string str, out NpgsqlTime time)
        {
            try
            {
                time = Parse(str);
                return true;
            }
            catch
            {
                time = AllBalls;
                return false;
            }
        }

        public int CompareTo(NpgsqlTime other)
        {
            return Ticks.CompareTo(other.Ticks);
        }

        public int CompareTo(object obj)
        {
            if (obj == null)
            {
                return 1;
            }
            if (obj is NpgsqlTime)
            {
                return CompareTo((NpgsqlTime) obj);
            }
            throw new ArgumentException();
        }

        public int Compare(NpgsqlTime x, NpgsqlTime y)
        {
            return x.CompareTo(y);
        }

        public int Compare(object x, object y)
        {
            if (x == null)
            {
                return y == null ? 0 : -1;
            }
            if (y == null)
            {
                return 1;
            }
            if (!(x is IComparable) || !(y is IComparable))
            {
                throw new ArgumentException();
            }
            return ((IComparable) x).CompareTo(y);
        }

        public static bool operator ==(NpgsqlTime x, NpgsqlTime y)
        {
            return x.Equals(y);
        }

        public static bool operator !=(NpgsqlTime x, NpgsqlTime y)
        {
            return !(x == y);
        }

        public static bool operator <(NpgsqlTime x, NpgsqlTime y)
        {
            return x.Ticks < y.Ticks;
        }

        public static bool operator >(NpgsqlTime x, NpgsqlTime y)
        {
            return x.Ticks > y.Ticks;
        }

        public static bool operator <=(NpgsqlTime x, NpgsqlTime y)
        {
            return x.Ticks <= y.Ticks;
        }

        public static bool operator >=(NpgsqlTime x, NpgsqlTime y)
        {
            return x.Ticks >= y.Ticks;
        }

        public static explicit operator NpgsqlInterval(NpgsqlTime time)
        {
            return new NpgsqlInterval(time.Ticks);
        }

        public static explicit operator NpgsqlTime(NpgsqlInterval interval)
        {
            return new NpgsqlTime(interval);
        }

        public static explicit operator TimeSpan(NpgsqlTime time)
        {
            return new TimeSpan(time.Ticks);
        }

        public static explicit operator DateTime(NpgsqlTime time)
        {
            try
            {
                return new DateTime(time.Ticks, DateTimeKind.Unspecified);
            }
            catch
            {
                throw new InvalidCastException();
            }
        }

        public static explicit operator NpgsqlTime(TimeSpan interval)
        {
            return new NpgsqlTime(interval);
        }

        public NpgsqlTime AddTicks(long ticksAdded)
        {
            return new NpgsqlTime((Ticks + ticksAdded)%NpgsqlInterval.TicksPerDay);
        }

        private NpgsqlTime AddTicks(long ticksAdded, out int overflow)
        {
            long result = Ticks + ticksAdded;
            overflow = (int) (result/NpgsqlInterval.TicksPerDay);
            result %= NpgsqlInterval.TicksPerDay;
            if (result < 0)
            {
                --overflow; //"carry the one"
            }
            return new NpgsqlTime(result);
        }

        public NpgsqlTime Add(NpgsqlInterval interval)
        {
            return AddTicks(interval.Ticks);
        }

        internal NpgsqlTime Add(NpgsqlInterval interval, out int overflow)
        {
            return AddTicks(interval.Ticks, out overflow);
        }

        public NpgsqlTime Subtract(NpgsqlInterval interval)
        {
            return AddTicks(-interval.Ticks);
        }

        public NpgsqlInterval Subtract(NpgsqlTime earlier)
        {
            return new NpgsqlInterval(Ticks - earlier.Ticks);
        }

        public NpgsqlTimeTZ AtTimeZone(NpgsqlTimeZone timeZone)
        {
            return new NpgsqlTimeTZ(this).AtTimeZone(timeZone);
        }

        public static NpgsqlTime operator +(NpgsqlTime time, NpgsqlInterval interval)
        {
            return time.Add(interval);
        }

        public static NpgsqlTime operator +(NpgsqlInterval interval, NpgsqlTime time)
        {
            return time + interval;
        }

        public static NpgsqlTime operator -(NpgsqlTime time, NpgsqlInterval interval)
        {
            return time.Subtract(interval);
        }

        public static NpgsqlInterval operator -(NpgsqlTime later, NpgsqlTime earlier)
        {
            return later.Subtract(earlier);
        }
    }

    [Serializable]
    public struct NpgsqlTimeTZ : IEquatable<NpgsqlTimeTZ>, IComparable<NpgsqlTimeTZ>, IComparable, IComparer<NpgsqlTimeTZ>,
                                 IComparer
    {
        public static readonly NpgsqlTimeTZ AllBalls = new NpgsqlTimeTZ(NpgsqlTime.AllBalls, NpgsqlTimeZone.UTC);

        public static NpgsqlTimeTZ Now
        {
            get { return new NpgsqlTimeTZ(NpgsqlTime.Now); }
        }

        public static NpgsqlTimeTZ LocalMidnight(NpgsqlDate date)
        {
            return new NpgsqlTimeTZ(NpgsqlTime.AllBalls, NpgsqlTimeZone.LocalTimeZone(date));
        }

        private readonly NpgsqlTime _localTime;
        private readonly NpgsqlTimeZone _timeZone;

        public NpgsqlTimeTZ(NpgsqlTime localTime, NpgsqlTimeZone timeZone)
        {
            _localTime = localTime;
            _timeZone = timeZone;
        }

        public NpgsqlTimeTZ(NpgsqlTime localTime)
            : this(localTime, NpgsqlTimeZone.CurrentTimeZone)
        {
        }

        public NpgsqlTimeTZ(long ticks)
            : this(new NpgsqlTime(ticks))
        {
        }

        public NpgsqlTimeTZ(TimeSpan time)
            : this(new NpgsqlTime(time))
        {
        }

        public NpgsqlTimeTZ(NpgsqlInterval time)
            : this(new NpgsqlTime(time))
        {
        }

        public NpgsqlTimeTZ(NpgsqlTimeTZ copyFrom)
            : this(copyFrom._localTime, copyFrom._timeZone)
        {
        }

        public NpgsqlTimeTZ(int hours, int minutes, int seconds)
            : this(new NpgsqlTime(hours, minutes, seconds))
        {
        }

        public NpgsqlTimeTZ(int hours, int minutes, int seconds, int microseconds)
            : this(new NpgsqlTime(hours, minutes, seconds, microseconds))
        {
        }

        public NpgsqlTimeTZ(int hours, int minutes, decimal seconds)
            : this(new NpgsqlTime(hours, minutes, seconds))
        {
        }

        public NpgsqlTimeTZ(int hours, int minutes, double seconds)
            : this(new NpgsqlTime(hours, minutes, seconds))
        {
        }

        public NpgsqlTimeTZ(long ticks, NpgsqlTimeZone timeZone)
            : this(new NpgsqlTime(ticks), timeZone)
        {
        }

        public NpgsqlTimeTZ(TimeSpan time, NpgsqlTimeZone timeZone)
            : this(new NpgsqlTime(time), timeZone)
        {
        }

        public NpgsqlTimeTZ(NpgsqlInterval time, NpgsqlTimeZone timeZone)
            : this(new NpgsqlTime(time), timeZone)
        {
        }

        public NpgsqlTimeTZ(int hours, int minutes, int seconds, NpgsqlTimeZone timeZone)
            : this(new NpgsqlTime(hours, minutes, seconds), timeZone)
        {
        }

        public NpgsqlTimeTZ(int hours, int minutes, int seconds, int microseconds, NpgsqlTimeZone timeZone)
            : this(new NpgsqlTime(hours, minutes, seconds, microseconds), timeZone)
        {
        }

        public NpgsqlTimeTZ(int hours, int minutes, decimal seconds, NpgsqlTimeZone timeZone)
            : this(new NpgsqlTime(hours, minutes, seconds), timeZone)
        {
        }

        public NpgsqlTimeTZ(int hours, int minutes, double seconds, NpgsqlTimeZone timeZone)
            : this(new NpgsqlTime(hours, minutes, seconds), timeZone)
        {
        }

        public override string ToString()
        {
            return string.Format("{0}{1}", _localTime, _timeZone);
        }

        public static NpgsqlTimeTZ Parse(string str)
        {
            if (str == null)
            {
                throw new ArgumentNullException();
            }
            try
            {
                int idx = Math.Max(str.IndexOf('+'), str.IndexOf('-'));
                if (idx == -1)
                {
                    throw new FormatException();
                }
                return new NpgsqlTimeTZ(NpgsqlTime.Parse(str.Substring(0, idx)), NpgsqlTimeZone.Parse(str.Substring(idx)));
            }
            catch (OverflowException)
            {
                throw;
            }
            catch
            {
                throw new FormatException();
            }
        }

        public NpgsqlTime LocalTime
        {
            get { return _localTime; }
        }

        public NpgsqlTimeZone TimeZone
        {
            get { return _timeZone; }
        }

        public NpgsqlTime UTCTime
        {
            get { return AtTimeZone(NpgsqlTimeZone.UTC).LocalTime; }
        }

        public NpgsqlTimeTZ AtTimeZone(NpgsqlTimeZone timeZone)
        {
            return new NpgsqlTimeTZ(LocalTime - _timeZone + timeZone, timeZone);
        }

        internal NpgsqlTimeTZ AtTimeZone(NpgsqlTimeZone timeZone, out int overflow)
        {
            return
                new NpgsqlTimeTZ(LocalTime.Add(timeZone - (NpgsqlInterval) (_timeZone), out overflow), timeZone);
        }

        public long Ticks
        {
            get { return _localTime.Ticks; }
        }

        /// <summary>
        /// Gets the number of whole microseconds held in the instance.
        /// <returns>An integer in the range [0, 999999].</returns>
        /// </summary>
        public int Microseconds
        {
            get { return _localTime.Microseconds; }
        }

        /// <summary>
        /// Gets the number of whole milliseconds held in the instance.
        /// <returns>An integer in the range [0, 999].</returns>
        /// </summary>
        public int Milliseconds
        {
            get { return _localTime.Milliseconds; }
        }

        /// <summary>
        /// Gets the number of whole seconds held in the instance.
        /// <returns>An interger in the range [0, 59].</returns>
        /// </summary>
        public int Seconds
        {
            get { return _localTime.Seconds; }
        }

        /// <summary>
        /// Gets the number of whole minutes held in the instance.
        /// <returns>An integer in the range [0, 59].</returns>
        /// </summary>
        public int Minutes
        {
            get { return _localTime.Minutes; }
        }

        /// <summary>
        /// Gets the number of whole hours held in the instance.
        /// <remarks>Note that the time 24:00:00 can be stored for roundtrip compatibility. Any calculations on such a
        /// value will normalised it to 00:00:00.</remarks>
        /// </summary>
        public int Hours
        {
            get { return _localTime.Hours; }
        }

        /// <summary>
        /// Normalise this time; if it is 24:00:00, convert it to 00:00:00
        /// </summary>
        /// <returns>This time, normalised</returns>
        public NpgsqlTimeTZ Normalize()
        {
            return new NpgsqlTimeTZ(_localTime.Normalize(), _timeZone);
        }

        public bool Equals(NpgsqlTimeTZ other)
        {
            return _localTime.Equals(other._localTime) && _timeZone.Equals(other._timeZone);
        }

        public override bool Equals(object obj)
        {
            return obj != null && obj is NpgsqlTimeTZ && Equals((NpgsqlTimeTZ) obj);
        }

        public override int GetHashCode()
        {
            return _localTime.GetHashCode() ^ PGUtil.RotateShift(_timeZone.GetHashCode(), 24);
        }

        /// <summary>
        /// Compares this with another <see cref="NpgsqlTimeTZ"/>. As per postgres' rules,
        /// first the times are compared as if they were both in the same timezone. If they are equal then
        /// then timezones are compared (+01:00 being "smaller" than -01:00).
        /// </summary>
        /// <param name="other">the <see cref="NpgsqlTimeTZ"/> to compare with.</param>
        /// <returns>An integer which is 0 if they are equal, &lt; 0 if this is the smaller and &gt; 0 if this is the larger.</returns>
        public int CompareTo(NpgsqlTimeTZ other)
        {
            int cmp = AtTimeZone(NpgsqlTimeZone.UTC).LocalTime.CompareTo(other.AtTimeZone(NpgsqlTimeZone.UTC).LocalTime);
            return cmp == 0 ? _timeZone.CompareTo(other._timeZone) : cmp;
        }

        public int CompareTo(object obj)
        {
            if (obj == null)
            {
                return 1;
            }
            if (obj is NpgsqlTimeTZ)
            {
                return CompareTo((NpgsqlTimeTZ) obj);
            }
            throw new ArgumentException();
        }

        public int Compare(NpgsqlTimeTZ x, NpgsqlTimeTZ y)
        {
            return x.CompareTo(y);
        }

        public int Compare(object x, object y)
        {
            if (x == null)
            {
                return y == null ? 0 : -1;
            }
            if (y == null)
            {
                return 1;
            }
            if (!(x is IComparable) || !(y is IComparable))
            {
                throw new ArgumentException();
            }
            return ((IComparable) x).CompareTo(y);
        }

        public static bool operator ==(NpgsqlTimeTZ x, NpgsqlTimeTZ y)
        {
            return x.Equals(y);
        }

        public static bool operator !=(NpgsqlTimeTZ x, NpgsqlTimeTZ y)
        {
            return !(x == y);
        }

        public static bool operator <(NpgsqlTimeTZ x, NpgsqlTimeTZ y)
        {
            return x.CompareTo(y) < 0;
        }

        public static bool operator >(NpgsqlTimeTZ x, NpgsqlTimeTZ y)
        {
            return x.CompareTo(y) > 0;
        }

        public static bool operator <=(NpgsqlTimeTZ x, NpgsqlTimeTZ y)
        {
            return x.CompareTo(y) <= 0;
        }

        public static bool operator >=(NpgsqlTimeTZ x, NpgsqlTimeTZ y)
        {
            return x.CompareTo(y) >= 0;
        }

        public NpgsqlTimeTZ Add(NpgsqlInterval interval)
        {
            return new NpgsqlTimeTZ(_localTime.Add(interval), _timeZone);
        }

        internal NpgsqlTimeTZ Add(NpgsqlInterval interval, out int overflow)
        {
            return new NpgsqlTimeTZ(_localTime.Add(interval, out overflow), _timeZone);
        }

        public NpgsqlTimeTZ Subtract(NpgsqlInterval interval)
        {
            return new NpgsqlTimeTZ(_localTime.Subtract(interval), _timeZone);
        }

        public NpgsqlInterval Subtract(NpgsqlTimeTZ earlier)
        {
            return _localTime.Subtract(earlier.AtTimeZone(_timeZone)._localTime);
        }

        public static NpgsqlTimeTZ operator +(NpgsqlTimeTZ time, NpgsqlInterval interval)
        {
            return time.Add(interval);
        }

        public static NpgsqlTimeTZ operator +(NpgsqlInterval interval, NpgsqlTimeTZ time)
        {
            return time + interval;
        }

        public static NpgsqlTimeTZ operator -(NpgsqlTimeTZ time, NpgsqlInterval interval)
        {
            return time.Subtract(interval);
        }

        public static NpgsqlInterval operator -(NpgsqlTimeTZ later, NpgsqlTimeTZ earlier)
        {
            return later.Subtract(earlier);
        }

        public static explicit operator NpgsqlTimeTZ(TimeSpan time)
        {
            return new NpgsqlTimeTZ(new NpgsqlTime(time));
        }

        public static explicit operator TimeSpan(NpgsqlTimeTZ time)
        {
            return (TimeSpan) time.LocalTime;
        }

        public static explicit operator DateTime(NpgsqlTimeTZ time)
        {
            // LocalTime property is actually time local to TimeZone
            return new DateTime(time.AtTimeZone(NpgsqlTimeZone.CurrentTimeZone).Ticks, DateTimeKind.Local);
        }
    }

    [Serializable]
    public struct NpgsqlTimeStamp : IEquatable<NpgsqlTimeStamp>, IComparable<NpgsqlTimeStamp>, IComparable,
                                    IComparer<NpgsqlTimeStamp>, IComparer
    {
        private enum TimeType
        {
            Finite,
            Infinity,
            MinusInfinity
        }

        public static readonly NpgsqlTimeStamp Epoch = new NpgsqlTimeStamp(NpgsqlDate.Epoch);
        public static readonly NpgsqlTimeStamp Era = new NpgsqlTimeStamp(NpgsqlDate.Era);

        public static readonly NpgsqlTimeStamp Infinity =
            new NpgsqlTimeStamp(TimeType.Infinity, NpgsqlDate.Era, NpgsqlTime.AllBalls);

        public static readonly NpgsqlTimeStamp MinusInfinity =
            new NpgsqlTimeStamp(TimeType.MinusInfinity, NpgsqlDate.Era, NpgsqlTime.AllBalls);

        public static NpgsqlTimeStamp Now
        {
            get { return new NpgsqlTimeStamp(NpgsqlDate.Now, NpgsqlTime.Now); }
        }

        public static NpgsqlTimeStamp Today
        {
            get { return new NpgsqlTimeStamp(NpgsqlDate.Now); }
        }

        public static NpgsqlTimeStamp Yesterday
        {
            get { return new NpgsqlTimeStamp(NpgsqlDate.Yesterday); }
        }

        public static NpgsqlTimeStamp Tomorrow
        {
            get { return new NpgsqlTimeStamp(NpgsqlDate.Tomorrow); }
        }

        private readonly NpgsqlDate _date;
        private readonly NpgsqlTime _time;
        private readonly TimeType _type;

        private NpgsqlTimeStamp(TimeType type, NpgsqlDate date, NpgsqlTime time)
        {
            _type = type;
            _date = date;
            _time = time;
        }

        public NpgsqlTimeStamp(NpgsqlDate date, NpgsqlTime time)
            : this(TimeType.Finite, date, time)
        {
        }

        public NpgsqlTimeStamp(NpgsqlDate date)
            : this(date, NpgsqlTime.AllBalls)
        {
        }

        public NpgsqlTimeStamp(int year, int month, int day, int hours, int minutes, int seconds)
            : this(new NpgsqlDate(year, month, day), new NpgsqlTime(hours, minutes, seconds))
        {
        }

        public NpgsqlDate Date
        {
            get { return _date; }
        }

        public NpgsqlTime Time
        {
            get { return _time; }
        }

        public int DayOfYear
        {
            get { return _date.DayOfYear; }
        }

        public int Year
        {
            get { return _date.Year; }
        }

        public int Month
        {
            get { return _date.Month; }
        }

        public int Day
        {
            get { return _date.Day; }
        }

        public DayOfWeek DayOfWeek
        {
            get { return _date.DayOfWeek; }
        }

        public bool IsLeapYear
        {
            get { return _date.IsLeapYear; }
        }

        public NpgsqlTimeStamp AddDays(int days)
        {
            switch (_type)
            {
                case TimeType.Infinity:
                case TimeType.MinusInfinity:
                    return this;
                default:
                    return new NpgsqlTimeStamp(_date.AddDays(days), _time);
            }
        }

        public NpgsqlTimeStamp AddYears(int years)
        {
            switch (_type)
            {
                case TimeType.Infinity:
                case TimeType.MinusInfinity:
                    return this;
                default:
                    return new NpgsqlTimeStamp(_date.AddYears(years), _time);
            }
        }

        public NpgsqlTimeStamp AddMonths(int months)
        {
            switch (_type)
            {
                case TimeType.Infinity:
                case TimeType.MinusInfinity:
                    return this;
                default:
                    return new NpgsqlTimeStamp(_date.AddMonths(months), _time);
            }
        }

        public long Ticks
        {
            get { return _date.DaysSinceEra*NpgsqlInterval.TicksPerDay + _time.Ticks; }
        }

        public int Microseconds
        {
            get { return _time.Microseconds; }
        }

        public int Milliseconds
        {
            get { return _time.Milliseconds; }
        }

        public int Seconds
        {
            get { return _time.Seconds; }
        }

        public int Minutes
        {
            get { return _time.Minutes; }
        }

        public int Hours
        {
            get { return _time.Hours; }
        }

        public bool IsFinite
        {
            get { return _type == TimeType.Finite; }
        }

        public bool IsInfinity
        {
            get { return _type == TimeType.Infinity; }
        }

        public bool IsMinusInfinity
        {
            get { return _type == TimeType.MinusInfinity; }
        }

        public NpgsqlTimeStamp Normalize()
        {
            return Add(NpgsqlInterval.Zero);
        }

        public override string ToString()
        {
            switch (_type)
            {
                case TimeType.Infinity:
                    return "infinity";
                case TimeType.MinusInfinity:
                    return "-infinity";
                default:
                    return string.Format("{0} {1}", _date, _time);
            }
        }

        public static NpgsqlTimeStamp Parse(string str)
        {
            if (str == null)
            {
                throw new NullReferenceException();
            }
            switch (str = str.Trim().ToLowerInvariant())
            {
                case "infinity":
                    return Infinity;
                case "-infinity":
                    return MinusInfinity;
                default:
                    try
                    {
                        int idxSpace = str.IndexOf(' ');
                        string datePart = str.Substring(0, idxSpace);
                        if (str.Contains("bc"))
                        {
                            datePart += " BC";
                        }
                        int idxSecond = str.IndexOf(' ', idxSpace + 1);
                        if (idxSecond == -1)
                        {
                            idxSecond = str.Length;
                        }
                        string timePart = str.Substring(idxSpace + 1, idxSecond - idxSpace - 1);
                        return new NpgsqlTimeStamp(NpgsqlDate.Parse(datePart), NpgsqlTime.Parse(timePart));
                    }
                    catch (OverflowException)
                    {
                        throw;
                    }
                    catch
                    {
                        throw new FormatException();
                    }
            }
        }

        public bool Equals(NpgsqlTimeStamp other)
        {
            switch (_type)
            {
                case TimeType.Infinity:
                    return other._type == TimeType.Infinity;
                case TimeType.MinusInfinity:
                    return other._type == TimeType.MinusInfinity;
                default:
                    return other._type == TimeType.Finite && _date.Equals(other._date) && _time.Equals(other._time);
            }
        }

        public override bool Equals(object obj)
        {
            return obj != null && obj is NpgsqlTimeStamp && Equals((NpgsqlTimeStamp) obj);
        }

        public override int GetHashCode()
        {
            switch (_type)
            {
                case TimeType.Infinity:
                    return int.MaxValue;
                case TimeType.MinusInfinity:
                    return int.MinValue;
                default:
                    return _date.GetHashCode() ^ PGUtil.RotateShift(_time.GetHashCode(), 16);
            }
        }

        public int CompareTo(NpgsqlTimeStamp other)
        {
            switch (_type)
            {
                case TimeType.Infinity:
                    return other._type == TimeType.Infinity ? 0 : 1;
                case TimeType.MinusInfinity:
                    return other._type == TimeType.MinusInfinity ? 0 : -1;
                default:
                    switch (other._type)
                    {
                        case TimeType.Infinity:
                            return -1;
                        case TimeType.MinusInfinity:
                            return 1;
                        default:
                            int cmp = _date.CompareTo(other._date);
                            return cmp == 0 ? _time.CompareTo(_time) : cmp;
                    }
            }
        }

        public int CompareTo(object obj)
        {
            if (obj == null)
            {
                return 1;
            }
            if (obj is NpgsqlTimeStamp)
            {
                return CompareTo((NpgsqlTimeStamp) obj);
            }
            throw new ArgumentException();
        }

        public int Compare(NpgsqlTimeStamp x, NpgsqlTimeStamp y)
        {
            return x.CompareTo(y);
        }

        public int Compare(object x, object y)
        {
            if (x == null)
            {
                return y == null ? 0 : -1;
            }
            if (y == null)
            {
                return 1;
            }
            if (!(x is IComparable) || !(y is IComparable))
            {
                throw new ArgumentException();
            }
            return ((IComparable) x).CompareTo(y);
        }

        public NpgsqlTimeStampTZ AtTimeZone(NpgsqlTimeZone timeZoneFrom, NpgsqlTimeZone timeZoneTo)
        {
            int overflow;
            NpgsqlTimeTZ adjusted = new NpgsqlTimeTZ(_time, timeZoneFrom).AtTimeZone(timeZoneTo, out overflow);
            return new NpgsqlTimeStampTZ(_date.AddDays(overflow), adjusted);
        }

        public NpgsqlTimeStampTZ AtTimeZone(NpgsqlTimeZone timeZone)
        {
            return AtTimeZone(timeZone, NpgsqlTimeZone.LocalTimeZone(_date));
        }

        public NpgsqlTimeStamp Add(NpgsqlInterval interval)
        {
            switch (_type)
            {
                case TimeType.Infinity:
                case TimeType.MinusInfinity:
                    return this;
                default:
                    int overflow;
                    NpgsqlTime time = _time.Add(interval, out overflow);
                    return new NpgsqlTimeStamp(_date.Add(interval, overflow), time);
            }
        }

        public NpgsqlTimeStamp Subtract(NpgsqlInterval interval)
        {
            return Add(-interval);
        }

        public NpgsqlInterval Subtract(NpgsqlTimeStamp timestamp)
        {
            switch (_type)
            {
                case TimeType.Infinity:
                case TimeType.MinusInfinity:
                    throw new ArgumentOutOfRangeException("this", "You cannot subtract infinity timestamps");
            }
            switch (timestamp._type)
            {
                case TimeType.Infinity:
                case TimeType.MinusInfinity:
                    throw new ArgumentOutOfRangeException("timestamp", "You cannot subtract infinity timestamps");
            }
            return new NpgsqlInterval(0, _date.DaysSinceEra - timestamp._date.DaysSinceEra, _time.Ticks - timestamp._time.Ticks);
        }

        public static implicit operator NpgsqlTimeStamp(DateTime datetime)
        {
            if (datetime == DateTime.MaxValue)
            {
                return Infinity;
            }
            else if (datetime == DateTime.MinValue)
            {
                return MinusInfinity;
            }
            else
            {
                return new NpgsqlTimeStamp(new NpgsqlDate(datetime), new NpgsqlTime(datetime.TimeOfDay));
            }
        }

        public static implicit operator DateTime(NpgsqlTimeStamp timestamp)
        {
            switch (timestamp._type)
            {
                case TimeType.Infinity:
                    return DateTime.MaxValue;
                case TimeType.MinusInfinity:
                    return DateTime.MinValue;
                default:
                    try
                    {
                        return
                            new DateTime(timestamp.Date.DaysSinceEra*NpgsqlInterval.TicksPerDay + timestamp._time.Ticks,
                                         DateTimeKind.Unspecified);
                    }
                    catch
                    {
                        throw new InvalidCastException();
                    }
            }
        }

        public static NpgsqlTimeStamp operator +(NpgsqlTimeStamp timestamp, NpgsqlInterval interval)
        {
            return timestamp.Add(interval);
        }

        public static NpgsqlTimeStamp operator +(NpgsqlInterval interval, NpgsqlTimeStamp timestamp)
        {
            return timestamp.Add(interval);
        }

        public static NpgsqlTimeStamp operator -(NpgsqlTimeStamp timestamp, NpgsqlInterval interval)
        {
            return timestamp.Subtract(interval);
        }

        public static NpgsqlInterval operator -(NpgsqlTimeStamp x, NpgsqlTimeStamp y)
        {
            return x.Subtract(y);
        }

        public static bool operator ==(NpgsqlTimeStamp x, NpgsqlTimeStamp y)
        {
            return x.Equals(y);
        }

        public static bool operator !=(NpgsqlTimeStamp x, NpgsqlTimeStamp y)
        {
            return !(x == y);
        }

        public static bool operator <(NpgsqlTimeStamp x, NpgsqlTimeStamp y)
        {
            return x.CompareTo(y) < 0;
        }

        public static bool operator >(NpgsqlTimeStamp x, NpgsqlTimeStamp y)
        {
            return x.CompareTo(y) > 0;
        }

        public static bool operator <=(NpgsqlTimeStamp x, NpgsqlTimeStamp y)
        {
            return x.CompareTo(y) <= 0;
        }

        public static bool operator >=(NpgsqlTimeStamp x, NpgsqlTimeStamp y)
        {
            return x.CompareTo(y) >= 0;
        }
    }

    [Serializable]
    public struct NpgsqlTimeStampTZ : IEquatable<NpgsqlTimeStampTZ>, IComparable<NpgsqlTimeStampTZ>, IComparable,
                                      IComparer<NpgsqlTimeStampTZ>, IComparer
    {
        private enum TimeType
        {
            Finite,
            Infinity,
            MinusInfinity
        }

        public static readonly NpgsqlTimeStampTZ Epoch = new NpgsqlTimeStampTZ(NpgsqlDate.Epoch, NpgsqlTimeTZ.AllBalls);
        public static readonly NpgsqlTimeStampTZ Era = new NpgsqlTimeStampTZ(NpgsqlDate.Era, NpgsqlTimeTZ.AllBalls);

        public static readonly NpgsqlTimeStampTZ Infinity =
            new NpgsqlTimeStampTZ(TimeType.Infinity, NpgsqlDate.Era, NpgsqlTimeTZ.AllBalls);

        public static readonly NpgsqlTimeStampTZ MinusInfinity =
            new NpgsqlTimeStampTZ(TimeType.MinusInfinity, NpgsqlDate.Era, NpgsqlTimeTZ.AllBalls);

        public static NpgsqlTimeStampTZ Now
        {
            get { return new NpgsqlTimeStampTZ(NpgsqlDate.Now, NpgsqlTimeTZ.Now); }
        }

        public static NpgsqlTimeStampTZ Today
        {
            get { return new NpgsqlTimeStampTZ(NpgsqlDate.Now); }
        }

        public static NpgsqlTimeStampTZ Yesterday
        {
            get { return new NpgsqlTimeStampTZ(NpgsqlDate.Yesterday); }
        }

        public static NpgsqlTimeStampTZ Tomorrow
        {
            get { return new NpgsqlTimeStampTZ(NpgsqlDate.Tomorrow); }
        }

        private readonly NpgsqlDate _date;
        private readonly NpgsqlTimeTZ _time;
        private readonly TimeType _type;

        private NpgsqlTimeStampTZ(TimeType type, NpgsqlDate date, NpgsqlTimeTZ time)
        {
            _type = type;
            _date = date;
            _time = time;
        }

        public NpgsqlTimeStampTZ(NpgsqlDate date, NpgsqlTimeTZ time)
            : this(TimeType.Finite, date, time)
        {
        }

        public NpgsqlTimeStampTZ(NpgsqlDate date)
            : this(date, NpgsqlTimeTZ.LocalMidnight(date))
        {
        }

        public NpgsqlTimeStampTZ(int year, int month, int day, int hours, int minutes, int seconds, NpgsqlTimeZone? timezone)
            : this(
                new NpgsqlDate(year, month, day),
                new NpgsqlTimeTZ(hours, minutes, seconds,
                                 timezone.HasValue ? timezone.Value : NpgsqlTimeZone.LocalTimeZone(new NpgsqlDate(year, month, day)))
                )
        {
        }

        public NpgsqlDate Date
        {
            get { return _date; }
        }

        public NpgsqlTimeTZ Time
        {
            get { return _time; }
        }

        public int DayOfYear
        {
            get { return _date.DayOfYear; }
        }

        public int Year
        {
            get { return _date.Year; }
        }

        public int Month
        {
            get { return _date.Month; }
        }

        public int Day
        {
            get { return _date.Day; }
        }

        public DayOfWeek DayOfWeek
        {
            get { return _date.DayOfWeek; }
        }

        public bool IsLeapYear
        {
            get { return _date.IsLeapYear; }
        }

        public NpgsqlTimeStampTZ AddDays(int days)
        {
            switch (_type)
            {
                case TimeType.Infinity:
                case TimeType.MinusInfinity:
                    return this;
                default:
                    return new NpgsqlTimeStampTZ(_date.AddDays(days), _time);
            }
        }

        public NpgsqlTimeStampTZ AddYears(int years)
        {
            switch (_type)
            {
                case TimeType.Infinity:
                case TimeType.MinusInfinity:
                    return this;
                default:
                    return new NpgsqlTimeStampTZ(_date.AddYears(years), _time);
            }
        }

        public NpgsqlTimeStampTZ AddMonths(int months)
        {
            switch (_type)
            {
                case TimeType.Infinity:
                case TimeType.MinusInfinity:
                    return this;
                default:
                    return new NpgsqlTimeStampTZ(_date.AddMonths(months), _time);
            }
        }

        public NpgsqlTime LocalTime
        {
            get { return _time.LocalTime; }
        }

        public NpgsqlTimeZone TimeZone
        {
            get { return _time.TimeZone; }
        }

        public NpgsqlTime UTCTime
        {
            get { return _time.UTCTime; }
        }

        public long Ticks
        {
            get { return _date.DaysSinceEra*NpgsqlInterval.TicksPerDay + _time.Ticks; }
        }

        public int Microseconds
        {
            get { return _time.Microseconds; }
        }

        public int Milliseconds
        {
            get { return _time.Milliseconds; }
        }

        public int Seconds
        {
            get { return _time.Seconds; }
        }

        public int Minutes
        {
            get { return _time.Minutes; }
        }

        public int Hours
        {
            get { return _time.Hours; }
        }

        public bool IsFinite
        {
            get { return _type == TimeType.Finite; }
        }

        public bool IsInfinity
        {
            get { return _type == TimeType.Infinity; }
        }

        public bool IsMinusInfinity
        {
            get { return _type == TimeType.MinusInfinity; }
        }

        public NpgsqlTimeStampTZ Normalize()
        {
            return Add(NpgsqlInterval.Zero);
        }

        public override string ToString()
        {
            switch (_type)
            {
                case TimeType.Infinity:
                    return "infinity";
                case TimeType.MinusInfinity:
                    return "-infinity";
                default:
                    return string.Format("{0} {1}", _date, _time);
            }
        }

        public static NpgsqlTimeStampTZ Parse(string str)
        {
            if (str == null)
            {
                throw new NullReferenceException();
            }
            switch (str = str.Trim().ToLowerInvariant())
            {
                case "infinity":
                    return Infinity;
                case "-infinity":
                    return MinusInfinity;
                default:
                    try
                    {
                        int idxSpace = str.IndexOf(' ');
                        string datePart = str.Substring(0, idxSpace);
                        if (str.Contains("bc"))
                        {
                            datePart += " BC";
                        }
                        int idxSecond = str.IndexOf(' ', idxSpace + 1);
                        if (idxSecond == -1)
                        {
                            idxSecond = str.Length;
                        }
                        string timePart = str.Substring(idxSpace + 1, idxSecond - idxSpace - 1);
                        return new NpgsqlTimeStampTZ(NpgsqlDate.Parse(datePart), NpgsqlTimeTZ.Parse(timePart));
                    }
                    catch (OverflowException)
                    {
                        throw;
                    }
                    catch
                    {
                        throw new FormatException();
                    }
            }
        }

        public bool Equals(NpgsqlTimeStampTZ other)
        {
            switch (_type)
            {
                case TimeType.Infinity:
                    return other._type == TimeType.Infinity;
                case TimeType.MinusInfinity:
                    return other._type == TimeType.MinusInfinity;
                default:
                    return other._type == TimeType.Finite && _date.Equals(other._date) && _time.Equals(other._time);
            }
        }

        public override bool Equals(object obj)
        {
            return obj != null && obj is NpgsqlTimeStamp && Equals((NpgsqlTimeStampTZ) obj);
        }

        public override int GetHashCode()
        {
            switch (_type)
            {
                case TimeType.Infinity:
                    return int.MaxValue;
                case TimeType.MinusInfinity:
                    return int.MinValue;
                default:
                    return _date.GetHashCode() ^ PGUtil.RotateShift(_time.GetHashCode(), 16);
            }
        }

        public int CompareTo(NpgsqlTimeStampTZ other)
        {
            switch (_type)
            {
                case TimeType.Infinity:
                    return other._type == TimeType.Infinity ? 0 : 1;
                case TimeType.MinusInfinity:
                    return other._type == TimeType.MinusInfinity ? 0 : -1;
                default:
                    switch (other._type)
                    {
                        case TimeType.Infinity:
                            return -1;
                        case TimeType.MinusInfinity:
                            return 1;
                        default:
                            int cmp = _date.CompareTo(other._date);
                            return cmp == 0 ? _time.CompareTo(_time) : cmp;
                    }
            }
        }

        public int CompareTo(object obj)
        {
            if (obj == null)
            {
                return 1;
            }
            if (obj is NpgsqlTimeStamp)
            {
                return CompareTo((NpgsqlTimeStamp) obj);
            }
            throw new ArgumentException();
        }

        public int Compare(NpgsqlTimeStampTZ x, NpgsqlTimeStampTZ y)
        {
            return x.CompareTo(y);
        }

        public int Compare(object x, object y)
        {
            if (x == null)
            {
                return y == null ? 0 : -1;
            }
            if (y == null)
            {
                return 1;
            }
            if (!(x is IComparable) || !(y is IComparable))
            {
                throw new ArgumentException();
            }
            return ((IComparable) x).CompareTo(y);
        }

        public NpgsqlTimeStamp AtTimeZone(NpgsqlTimeZone timeZone)
        {
            int overflow;
            NpgsqlTimeTZ adjusted = _time.AtTimeZone(timeZone, out overflow);
            return new NpgsqlTimeStamp(_date.AddDays(overflow), adjusted.LocalTime);
        }

        public NpgsqlTimeStampTZ Add(NpgsqlInterval interval)
        {
            switch (_type)
            {
                case TimeType.Infinity:
                case TimeType.MinusInfinity:
                    return this;
                default:
                    int overflow;
                    NpgsqlTimeTZ time = _time.Add(interval, out overflow);
                    return new NpgsqlTimeStampTZ(_date.Add(interval, overflow), time);
            }
        }

        public NpgsqlTimeStampTZ Subtract(NpgsqlInterval interval)
        {
            return Add(-interval);
        }

        public NpgsqlInterval Subtract(NpgsqlTimeStampTZ timestamp)
        {
            switch (_type)
            {
                case TimeType.Infinity:
                case TimeType.MinusInfinity:
                    throw new ArgumentOutOfRangeException("this", "You cannot subtract infinity timestamps");
            }
            switch (timestamp._type)
            {
                case TimeType.Infinity:
                case TimeType.MinusInfinity:
                    throw new ArgumentOutOfRangeException("timestamp", "You cannot subtract infinity timestamps");
            }
            return new NpgsqlInterval(0, _date.DaysSinceEra - timestamp._date.DaysSinceEra, (_time - timestamp._time).Ticks);
        }

        public static implicit operator NpgsqlTimeStampTZ(DateTime datetime)
        {
            if (datetime == DateTime.MaxValue)
            {
                return Infinity;
            }
            else if (datetime == DateTime.MinValue)
            {
                return MinusInfinity;
            }
            else
            {
                NpgsqlDate newDate = new NpgsqlDate(datetime);
                return
                    new NpgsqlTimeStampTZ(newDate,
                                          new NpgsqlTimeTZ(datetime.TimeOfDay,
                                                           datetime.Kind == DateTimeKind.Utc
                                                               ? NpgsqlTimeZone.UTC
                                                               : NpgsqlTimeZone.LocalTimeZone(newDate)));
            }
        }

        public static explicit operator DateTime(NpgsqlTimeStampTZ timestamp)
        {
            switch (timestamp._type)
            {
                case TimeType.Infinity:
                    return DateTime.MaxValue;
                case TimeType.MinusInfinity:
                    return DateTime.MinValue;
                default:
                    try
                    {
                        NpgsqlTimeStamp utc = timestamp.AtTimeZone(NpgsqlTimeZone.UTC);
                        return new DateTime(utc.Date.DaysSinceEra*NpgsqlInterval.TicksPerDay + utc.Time.Ticks, DateTimeKind.Utc);
                    }
                    catch
                    {
                        throw new InvalidCastException();
                    }
            }
        }

        public static implicit operator NpgsqlTimeStampTZ(DateTimeOffset datetimeoffset)
        {
            if (datetimeoffset == DateTimeOffset.MaxValue)
            {
                return Infinity;
            }
            else if (datetimeoffset == DateTimeOffset.MinValue)
            {
                return MinusInfinity;
            }
            else
            {
                NpgsqlDate newDate = new NpgsqlDate(datetimeoffset.Year,
                    datetimeoffset.Month, datetimeoffset.Day);
                return
                    new NpgsqlTimeStampTZ(newDate, new NpgsqlTimeTZ(datetimeoffset.TimeOfDay,
                        new NpgsqlTimeZone(datetimeoffset.Offset)));
            }
        }
        public static explicit operator DateTimeOffset(NpgsqlTimeStampTZ timestamp)
        {
            switch (timestamp._type)
            {
                case TimeType.Infinity:
                    return DateTimeOffset.MaxValue;
                case TimeType.MinusInfinity:
                    return DateTimeOffset.MinValue;
                default:
                    try
                    {
                        return new DateTimeOffset(timestamp.Date.DaysSinceEra * NpgsqlInterval.TicksPerDay + timestamp.Time.Ticks, timestamp.TimeZone);
                    }
                    catch
                    {
                        throw new InvalidCastException();
                    }
            }
        }

        public static NpgsqlTimeStampTZ operator +(NpgsqlTimeStampTZ timestamp, NpgsqlInterval interval)
        {
            return timestamp.Add(interval);
        }

        public static NpgsqlTimeStampTZ operator +(NpgsqlInterval interval, NpgsqlTimeStampTZ timestamp)
        {
            return timestamp.Add(interval);
        }

        public static NpgsqlTimeStampTZ operator -(NpgsqlTimeStampTZ timestamp, NpgsqlInterval interval)
        {
            return timestamp.Subtract(interval);
        }

        public static NpgsqlInterval operator -(NpgsqlTimeStampTZ x, NpgsqlTimeStampTZ y)
        {
            return x.Subtract(y);
        }

        public static bool operator ==(NpgsqlTimeStampTZ x, NpgsqlTimeStampTZ y)
        {
            return x.Equals(y);
        }

        public static bool operator !=(NpgsqlTimeStampTZ x, NpgsqlTimeStampTZ y)
        {
            return !(x == y);
        }

        public static bool operator <(NpgsqlTimeStampTZ x, NpgsqlTimeStampTZ y)
        {
            return x.CompareTo(y) < 0;
        }

        public static bool operator >(NpgsqlTimeStampTZ x, NpgsqlTimeStampTZ y)
        {
            return x.CompareTo(y) > 0;
        }

        public static bool operator <=(NpgsqlTimeStampTZ x, NpgsqlTimeStampTZ y)
        {
            return x.CompareTo(y) <= 0;
        }

        public static bool operator >=(NpgsqlTimeStampTZ x, NpgsqlTimeStampTZ y)
        {
            return x.CompareTo(y) >= 0;
        }
    }
}

#pragma warning restore 1591
