[Runtime] Fixes #543

[Runtime] Fixes #543

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//
USING System
USING System.Runtime.Serialization
USING System.Globalization
USING System.Runtime.InteropServices
USING System.Runtime.Serialization
USING System.Runtime.CompilerServices
USING XSharp
USING System.Diagnostics

BEGIN NAMESPACE XSharp
	/// Internal type that implements the VO Compatible DATE type

	/// This type has many operators and implicit converters that normally are never directly called from user code.
	/// It holds year, month, day in 32 bits. For date calculations it uses the System.DateTime calculation logic.
	/// There are implicit converters between Date and DateTime.
	///
	/// <seealso cref="T:XSharp.IDate"/>
	/// <seealso cref="T:XSharp.RDD.DbDate"/>
	//[DebuggerTypeProxy(TYPEOF(DateDebugView))];
	[DebuggerDisplay("{ToDebugString(),nq}", Type := "DATE" )];
	[StructLayout(LayoutKind.Explicit,Pack := 1)];
    [Serializable];
	PUBLIC STRUCTURE __Date IMPLEMENTS System.IComparable, ;
		System.IFormattable, ;
		System.IConvertible, ;
		IDate, ;
		IComparable<__Date>, ;
		IEquatable<__Date>,;
        ISerializable
		// This structure uses an explicit layout to map
		// year, month and date into 32 bits
		// the _ymd field is for convenience, so we can do a single numeric comparison
		// to determine equality or relative comparisons in stead of doing 3 comparisons
		// for date calculation we use the Value PROPERTY which returns a System.DateTime type
		// Note that the Vulcan type uses a datetime which takes 8 bytes. We only use 4 bytes
		#region fields
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			[FieldOffset(00)] PRIVATE _ymd   AS System.Int32
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			[FieldOffset(00)] PRIVATE _year  AS System.UInt16
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			[FieldOffset(02)] PRIVATE _month AS System.Byte
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			[FieldOffset(03)] PRIVATE _day   AS System.Byte
		#endregion

		#region STATIC fields
			/// <exclude />
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			STATIC INITONLY PUBLIC  _NULL_DATE AS DATE
			/// <exclude />
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			CONST  CLIPPER_MIN_DATE := 2415386U AS DWORD	// 1901-01-01
			/// <exclude />
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			CONST  CLIPPER_MAX_DATE := 4606840U AS DWORD	// 7900-12-31
			/// <exclude />
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			STATIC INITONLY _dtCalc AS DateTime

		#endregion

		#region datetime conversions
            /// Return DATE value as DateTime.
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			PUBLIC PROPERTY Value AS System.DateTime
				GET
					IF (_ymd == 0)
						RETURN System.DateTime.MinValue
					ENDIF
					RETURN System.DateTime{_year, _month, _day}
				END GET
				SET
				    IF value != DateTime.MinValue
					    _year  := (WORD) value:Year
					    _month := (BYTE) value:Month
					    _day   := (BYTE) value:Day
				    ELSE
					    _ymd := 0
				    ENDIF
				END SET
			END PROPERTY
		#endregion

		#region constructors
			STATIC CONSTRUCTOR()
				_NULL_DATE  := DATE{}
				_dtCalc     := DateTime{1901,1,1}
				RETURN
			/// Construct a date from a DateTime value.
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			CONSTRUCTOR(dt AS System.DateTime)
                _ymd := _year := _month := _day := 0
                IF dt != DateTime.MinValue
				    _year  := (WORD) dt:Year
				    _month := (BYTE) dt:Month
				    _day   := (BYTE) dt:Day
                ELSE
                    _ymd := 0
                ENDIF
				RETURN

			/// Construct a date from another IDate type.
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			CONSTRUCTOR(d AS IDate)
                _ymd := 0
				_year  := (WORD) d:Year
				_month := (BYTE) d:Month
				_day   := (BYTE) d:Day
				RETURN

			/// Construct a date from a number of Ticks.
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			CONSTRUCTOR(ticks AS INT64)
				SELF(System.DateTime{ticks})
				RETURN

			/// Construct a date from a string. This assumes the string is in current Date format
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			CONSTRUCTOR(strDate AS STRING)
				LOCAL dValue := CToD(strDate) AS DATE
                _year := _month := _day := 0
				_ymd := dValue:_ymd

			/// Construct a date from a string using the specified Date Format.
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			CONSTRUCTOR(strDate AS STRING, strFormat AS STRING)
				LOCAL dValue := CToD(strDate,strFormat) AS DATE
                _year := _month := _day := 0
				_ymd := dValue:_ymd

			/// Construct a date from year, month, day
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			CONSTRUCTOR(year AS INT, month AS INT, day AS INT)
                _ymd := _year := _month := _day := 0
                IF year != 0 .AND. month != 0 .AND. day != 0
				    TRY
					    // this may throw an exception when the combination is not valid
                        IF (year == 1 .AND. month == 1 .AND. day == 1)
                            // this is DateTime.Mindate, but a valid date in VO
					        _year  := 1
					        _month := 1
					        _day   := 1
                        ELSE
  				            VAR dt := System.DateTime{year, month, day}
					        _year  := (WORD) dt:Year
					        _month := (BYTE) dt:Month
					        _day   := (BYTE) dt:Day
                        ENDIF
				    CATCH
        				_ymd := 0 // null_date
					    NOP
                     END TRY
                ELSE
				    _ymd := 0 // null_date
                ENDIF
				RETURN

			/// Construct a date from year, month, day
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			CONSTRUCTOR(year AS DWORD, month AS DWORD, day AS DWORD)
				// Chain to Int constructor
				SELF( (INT) year, (INT) month, (INT) day)
				RETURN

		#endregion

		#region methods
			/// <inheritdoc />
			METHOD CompareTo(o AS OBJECT) AS LONG
				VAR rhs := (DATE)o
                RETURN SELF:YMD:CompareTo(rhs:YMD)

			/// <inheritdoc />
			METHOD CompareTo(rhs AS DATE) AS LONG
                RETURN SELF:YMD:CompareTo(rhs:YMD)

			/// <inheritdoc />
			OVERRIDE METHOD GetHashCode() AS LONG
				RETURN _ymd:GetHashCode()

			/// <inheritdoc />
			METHOD GetTypeCode() AS System.TypeCode
				RETURN TypeCode.DateTime
		#endregion
		#region Operators
			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR ++(lhs AS DATE) AS DATE
				RETURN lhs:Add(1)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR +(lhs AS DATE, days AS USUAL) AS DATE
				RETURN lhs:Add(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR +(lhs AS DATE, days AS REAL8) AS DATE
				RETURN lhs:Add(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR +(lhs AS DATE, days AS LONG) AS DATE
				RETURN lhs:Add(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR +(lhs AS DATE, days AS INT64) AS DATE
				RETURN lhs:Add(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR +(lhs AS DATE, ts AS System.TimeSpan) AS DATE
				RETURN lhs:Add(ts)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR +(lhs AS DATE, days AS DWORD) AS DATE
				RETURN lhs:Add(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR +(lhs AS DATE, days AS UINT64) AS DATE
				RETURN lhs:Add(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR --(lhs AS DATE) AS DATE
				RETURN lhs:Subtract(1)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR -(lhs AS DATE, rhs AS DATE) AS LONG
				RETURN lhs:Subtract(rhs)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR -(lhs AS DATE, days AS USUAL) AS USUAL
				RETURN lhs:Subtract(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR -(lhs AS DATE, days AS REAL8) AS DATE
				RETURN lhs:Subtract(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR -(lhs AS DATE, days AS LONG) AS DATE
				RETURN lhs:Subtract(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR -(lhs AS DATE, days AS INT64) AS DATE
				RETURN lhs:Subtract(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR -(lhs AS DATE, ts AS System.TimeSpan) AS DATE
				RETURN lhs:Subtract(ts)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR -(lhs AS DATE, days AS DWORD) AS DATE
				RETURN lhs:Subtract(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR -(lhs AS DATE, days AS UINT64) AS DATE
				RETURN lhs:Subtract(days)

		#endregion
		#region Equality Operators

			/// <inheritdoc />
			METHOD Equals(lhs AS DATE) AS LOGIC
				RETURN _ymd == lhs:_ymd

			/// <inheritdoc />
			OVERRIDE METHOD Equals(o AS OBJECT) AS LOGIC
				IF o != NULL
					IF o:GetType() == TYPEOF(DATE)
						RETURN SELF:Equals(  (DATE) o)
					ELSEIF o:GetType() == TYPEOF(System.DateTime)
						RETURN SELF:Equals( DATE{ (System.DateTime) o})
					ENDIF
				ENDIF
			RETURN FALSE

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR ==(lhs AS DATE, rhs AS DATE) AS LOGIC
				RETURN (lhs:_ymd == rhs:_ymd)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR !=(lhs AS DATE, rhs AS DATE) AS LOGIC
				RETURN (lhs:_ymd != rhs:_ymd)

		#endregion
		#region Implicit and Explicit converters

			/// <include file="RTComments.xml" path="Comments/Converter/*" />
			STATIC OPERATOR EXPLICIT(dvalue AS DATE) AS DWORD
				// convert to julian date like vo
				// # of days since 1-1-1901 + 2415386
				LOCAL nDays AS DWORD
				nDays := (DWORD) (dvalue:Value - _dtCalc):Days + CLIPPER_MIN_DATE
				RETURN nDays

			/// <include file="RTComments.xml" path="Comments/Converter/*" />
			STATIC OPERATOR EXPLICIT(dValue AS DATE) AS INT
				LOCAL nDays AS LONG
				nDays := (INT) ((dValue:Value - _dtCalc):Days + CLIPPER_MIN_DATE)
				RETURN nDays

			/// <include file="RTComments.xml" path="Comments/Converter/*" />
			STATIC OPERATOR EXPLICIT(dw AS DWORD ) AS DATE
				// convert julian date to our date
				LOCAL result := _NULL_DATE AS DATE
				IF dw >= CLIPPER_MIN_DATE .AND. dw <= CLIPPER_MAX_DATE
					LOCAL dt AS DateTime
					dt := _dtCalc:AddDays ( (INT) dw -CLIPPER_MIN_DATE)
					result := DATE{dt}
				ENDIF
				RETURN result

			/// <include file="RTComments.xml" path="Comments/Converter/*" />
			STATIC OPERATOR EXPLICIT(i AS INT) AS DATE
				LOCAL result := _NULL_DATE AS DATE
				IF i >= CLIPPER_MIN_DATE .AND. i <= CLIPPER_MAX_DATE
					LOCAL dt AS DateTime
					dt := _dtCalc:AddDays ( i - CLIPPER_MIN_DATE)
					result := DATE{dt}
				ENDIF
				RETURN result

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR IMPLICIT(v AS System.DateTime) AS DATE
				RETURN DATE{v}

			/// <exclude />
			METHOD ToDateTime() AS System.DateTime
				RETURN SELF:Value

			/// <exclude />
			METHOD FromDateTime(dtvalue AS System.DateTime) AS DATE
				RETURN DATE{dtvalue}

		#endregion
		#region Comparison Operators
			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR >(lhs AS DATE, rhs AS DATE) AS LOGIC
				// note: cannot use _ymd for > comparison because that would depend
				//       on the processor layout
				IF lhs:_ymd == rhs:_ymd
					RETURN FALSE
				ELSEIF lhs:_year > rhs:_year
					RETURN TRUE
				ELSEIF lhs:_year == rhs:_year
					IF lhs:_month > rhs:_month
						RETURN TRUE
					ELSEIF lhs:_month == rhs:_month .AND. lhs:_day > rhs:_day
						RETURN TRUE
					ENDIF
				ENDIF
				RETURN FALSE

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR >=(lhs AS DATE, rhs AS DATE) AS LOGIC
				// note: cannot use _ymd for > comparison because that would depend
				//       on the processor layout
				IF lhs:_ymd == rhs:_ymd
					RETURN TRUE
				ELSEIF lhs:_year > rhs:_year
					RETURN TRUE
				ELSEIF lhs:_year == rhs:_year
					IF lhs:_month > rhs:_month
						RETURN TRUE
					ELSEIF lhs:_month == rhs:_month .AND. lhs:_day >= rhs:_day
						RETURN TRUE
					ENDIF
				ENDIF
				RETURN FALSE

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR IMPLICIT(v AS DATE) AS System.DateTime
				RETURN v:Value

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR <(lhs AS DATE, rhs AS DATE) AS LOGIC
				// note: cannot use _ymd for < comparison because that would depend
				//       on the processor layout
				IF lhs:_ymd == rhs:_ymd
					RETURN FALSE
				ELSEIF lhs:_year < rhs:_year
					RETURN TRUE
				ELSEIF lhs:_year == rhs:_year
					IF lhs:_month < rhs:_month
						RETURN TRUE
					ELSEIF lhs:_month == rhs:_month .AND. lhs:_day < rhs:_day
						RETURN TRUE
					ENDIF
				ENDIF
				RETURN FALSE

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR <=(lhs AS DATE, rhs AS DATE) AS LOGIC
				// note: cannot use _ymd for < comparison because that would depend
				//       on the processor layout
				IF lhs:_ymd == rhs:_ymd
					RETURN TRUE
				ELSEIF lhs:_year < rhs:_year
					RETURN TRUE
				ELSEIF lhs:_year == rhs:_year
					IF lhs:_month < rhs:_month
						RETURN TRUE
					ELSEIF lhs:_month == rhs:_month .AND. lhs:_day <= rhs:_day
						RETURN TRUE
					ENDIF
				ENDIF
				RETURN FALSE
		#endregion

		#region ADD and Subtract Methods
			/// <exclude />
			METHOD Add(days AS USUAL) AS DATE
				IF days:IsInteger
					RETURN SELF:Add( (INT64) days)
				ELSEIF days:IsNumeric
					 RETURN SELF:Add( (REAL8) days)
				ELSE
					THROW Error.ArgumentError(__ENTITY__,NAMEOF(days),1, "Incompatible argument for Date:Add()",{days})
				ENDIF
			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Add(days AS REAL8) AS DATE
				VAR res := SELF:Value:AddDays(days)
				RETURN DATE{res}
			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Add(days AS LONG) AS DATE
				VAR res := SELF:Value:AddDays(days)
				RETURN DATE{res}
			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Add(days AS INT64) AS DATE
				VAR res := SELF:Value:AddDays(days)
				RETURN DATE{res}

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Add(span AS System.TimeSpan) AS DATE
				VAR res := SELF:Value:Add(span)
				RETURN DATE{res}

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Add(days AS DWORD) AS DATE
				VAR res := SELF:Value:AddDays(days)
				RETURN DATE{res}

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Add(days AS UINT64) AS DATE
				VAR res := SELF:Value:AddDays(days)
				RETURN DATE{res}

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Subtract(lhs AS DATE) AS LONG
				LOCAL span AS System.TimeSpan
				span := (System.TimeSpan)(SELF:Value - lhs:Value)
				RETURN span:Days

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Subtract(days AS USUAL) AS USUAL
				IF days:IsInteger
					RETURN SELF:Subtract( -(INT64) days)
				ELSEIF days:IsNumeric
					 RETURN SELF:Subtract( -(REAL8) days)
				ELSEIF days:IsDate
					 RETURN SELF:Subtract( (DATE) days)
				ELSE
					THROW Error.ArgumentError(__ENTITY__,NAMEOF(days), 1, "Incompatible argument for Date:Subtract()", {days})
				ENDIF

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Subtract(days AS REAL8) AS DATE
				RETURN SELF:Add(-days)

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Subtract(days AS LONG) AS DATE
				RETURN SELF:Add(-days)

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Subtract(days AS INT64) AS DATE
				RETURN SELF:Add(-days)

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Subtract(ts AS System.TimeSpan) AS DATE
				RETURN SELF:Add(-ts)

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Subtract(days AS DWORD) AS DATE
				RETURN SELF:Add(-days)

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Subtract(days AS UINT64) AS DATE
				RETURN SELF:Add(-(INT64)days)
		#endregion

		#region IConvertable
			// forward most methods to the DateTime class so there will
			// be a proper (localized) error message
			/// <inheritdoc />
			METHOD IConvertible.ToBoolean(provider AS System.IFormatProvider) AS LOGIC
				RETURN ((IConvertible)SELF:Value):ToBoolean(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToByte(provider AS System.IFormatProvider) AS BYTE
				RETURN ((IConvertible)SELF:Value):ToByte(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToChar(provider AS System.IFormatProvider) AS CHAR
				RETURN ((IConvertible)SELF:Value):ToChar(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToDateTime(provider AS System.IFormatProvider) AS System.DateTime
				RETURN SELF:Value

			/// <inheritdoc />
			METHOD IConvertible.ToDecimal(provider AS System.IFormatProvider) AS Decimal
				RETURN ((IConvertible)SELF:Value):ToDecimal(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToDouble(provider AS System.IFormatProvider) AS REAL8
				RETURN ((IConvertible)SELF:Value):ToDouble(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToInt16(provider AS System.IFormatProvider) AS SHORT
				RETURN ((IConvertible)SELF:Value):ToInt16(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToInt32(provider AS System.IFormatProvider) AS LONG
				RETURN ((IConvertible)SELF:Value):ToInt32(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToInt64(provider AS System.IFormatProvider) AS INT64
				RETURN ((IConvertible)SELF:Value):ToInt64(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToSByte(provider AS System.IFormatProvider) AS SByte
				RETURN ((IConvertible)SELF:Value):ToSByte(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToSingle(provider AS System.IFormatProvider) AS REAL4
				RETURN ((IConvertible)SELF:Value):ToSingle(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToType(conversionType AS System.Type, provider AS System.IFormatProvider) AS OBJECT
				IF conversionType == TYPEOF(DATE)
					RETURN SELF
                ELSEIF conversionType == TYPEOF(STRING)
                    RETURN SELF:ToString()
				ELSEIF conversionType == TYPEOF(System.DateTime)
					RETURN SELF:Value
				ENDIF
				RETURN ((IConvertible)SELF:Value):ToType(conversionType, provider)

			/// <inheritdoc />
			METHOD IConvertible.ToUInt16(provider AS System.IFormatProvider) AS WORD
				RETURN ((IConvertible)SELF:Value):ToUInt16(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToUInt32(provider AS System.IFormatProvider) AS DWORD
				RETURN ((IConvertible)SELF:Value):ToUInt32(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToUInt64(provider AS System.IFormatProvider) AS UINT64
				RETURN ((IConvertible)SELF:Value):ToUInt64(provider)
		#endregion

		#region ToString()
			// Use DateTime ToString() methods as helpers
			/// <inheritdoc />
			OVERRIDE METHOD ToString() AS STRING
				IF (_ymd == 0)
					RETURN RuntimeState.NullDateString
				ENDIF
			    RETURN DToC(SELF)
			/// <inheritdoc />
			METHOD ToString(provider AS System.IFormatProvider) AS STRING
				IF (_ymd == 0)
					RETURN RuntimeState.NullDateString
				ENDIF
				RETURN SELF:Value:ToString(provider)
			/// <inheritdoc cref="M:System.DateTime.ToString(System.String)"/>
			METHOD ToString(s AS STRING) AS STRING
				IF (_ymd == 0)
					RETURN RuntimeState.NullDateString
				ENDIF
				RETURN SELF:Value:ToString(s)
			/// <inheritdoc />
			METHOD ToString(s AS STRING, fp AS System.IFormatProvider) AS STRING
				IF (_ymd == 0)
					RETURN RuntimeState.NullDateString
				ENDIF
				IF (s == NULL)
					s := XSharp.RuntimeState.GetValue<STRING>(Set.DateFormatNet)
				ENDIF
				RETURN SELF:Value:ToString(s, fp)
		#endregion

        #region ISerializable
        /// <inheritdoc/>
        PUBLIC METHOD GetObjectData(info AS SerializationInfo, context AS StreamingContext) AS VOID
            IF info == NULL
                THROW System.ArgumentException{"info"}
            ENDIF
            info:AddValue("Value", SELF:ToString())
            RETURN
        /// <include file="RTComments.xml" path="Comments/SerializeConstructor/*" />
        CONSTRUCTOR (info AS SerializationInfo, context AS StreamingContext)
            _ymd := _year := _month := _day := 0
            IF info == NULL
                THROW System.ArgumentException{"info"}
            ENDIF
            SELF := CToD(info:GetString("Value"))
        #endregion

        #region properties

			/// <inheritdoc />
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			PROPERTY IsEmpty AS LOGIC
				GET
					RETURN _ymd == 0
				END GET
			END PROPERTY
			/// <inheritdoc />
			PROPERTY Month	AS INT GET _month
			/// <inheritdoc />
			PROPERTY Year	AS INT GET _year
			/// <inheritdoc />
			PROPERTY Day	AS INT GET _day
			// Next properties for easy access in right type
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			INTERNAL PROPERTY DYear		AS DWORD GET _year

            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			INTERNAL PROPERTY DMonth	AS DWORD GET _month

            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			INTERNAL PROPERTY DDay		AS DWORD GET _day

            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			INTERNAL PROPERTY YMD AS DWORD GET ;
                (SELF:DYear * 0xFFFF)  + (SELF:DMonth *0xFF) + _day

            INTERNAL METHOD ToDebugString() AS STRING
				IF (_ymd == 0)
					RETURN "NULL_DATE"
                ENDIF
                RETURN SELF:Value:ToShortDateString()

		#endregion

		#region STATIC Properties

			/// <exclude />
			STATIC METHOD NullDate AS DATE
				RETURN  _NULL_DATE

		#endregion

	END STRUCTURE
END NAMESPACE

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//
USING System
USING System.Runtime.Serialization
USING System.Globalization
USING System.Runtime.InteropServices
USING System.Runtime.Serialization
USING System.Runtime.CompilerServices
USING XSharp
USING System.Diagnostics

BEGIN NAMESPACE XSharp
	/// Internal type that implements the VO Compatible DATE type

	/// This type has many operators and implicit converters that normally are never directly called from user code.
	/// It holds year, month, day in 32 bits. For date calculations it uses the System.DateTime calculation logic.
	/// There are implicit converters between Date and DateTime.
	///
	/// <seealso cref="T:XSharp.IDate"/>
	/// <seealso cref="T:XSharp.RDD.DbDate"/>
	//[DebuggerTypeProxy(TYPEOF(DateDebugView))];
	[DebuggerDisplay("{ToDebugString(),nq}", Type := "DATE" )];
	[StructLayout(LayoutKind.Explicit,Pack := 1)];
    [Serializable];
	PUBLIC STRUCTURE __Date IMPLEMENTS System.IComparable, ;
		System.IFormattable, ;
		System.IConvertible, ;
		IDate, ;
		IComparable<__Date>, ;
		IEquatable<__Date>,;
        ISerializable
		// This structure uses an explicit layout to map
		// year, month and date into 32 bits
		// the _ymd field is for convenience, so we can do a single numeric comparison
		// to determine equality or relative comparisons in stead of doing 3 comparisons
		// for date calculation we use the Value PROPERTY which returns a System.DateTime type
		// Note that the Vulcan type uses a datetime which takes 8 bytes. We only use 4 bytes
		#region fields
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			[FieldOffset(00)] PRIVATE _ymd   AS System.Int32
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			[FieldOffset(00)] PRIVATE _year  AS System.UInt16
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			[FieldOffset(02)] PRIVATE _month AS System.Byte
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			[FieldOffset(03)] PRIVATE _day   AS System.Byte
		#endregion

		#region STATIC fields
			/// <exclude />
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			STATIC INITONLY PUBLIC  _NULL_DATE AS DATE
			/// <exclude />
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			CONST  CLIPPER_MIN_DATE := 2415386U AS DWORD	// 1901-01-01
			/// <exclude />
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			CONST  CLIPPER_MAX_DATE := 4606840U AS DWORD	// 7900-12-31
			/// <exclude />
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			STATIC INITONLY _dtCalc AS DateTime

		#endregion

		#region datetime conversions
            /// Return DATE value as DateTime.
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			PUBLIC PROPERTY Value AS System.DateTime
				GET
					IF (_ymd == 0)
						RETURN System.DateTime.MinValue
					ENDIF
					RETURN System.DateTime{_year, _month, _day}
				END GET
				SET
				    IF value != DateTime.MinValue
					    _year  := (WORD) value:Year
					    _month := (BYTE) value:Month
					    _day   := (BYTE) value:Day
				    ELSE
					    _ymd := 0
				    ENDIF
				END SET
			END PROPERTY
		#endregion

		#region constructors
			STATIC CONSTRUCTOR()
				_NULL_DATE  := DATE{}
				_dtCalc     := DateTime{1901,1,1}
				RETURN
			/// Construct a date from a DateTime value.
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			CONSTRUCTOR(dt AS System.DateTime)
                _ymd := _year := _month := _day := 0
                IF dt != DateTime.MinValue
				    _year  := (WORD) dt:Year
				    _month := (BYTE) dt:Month
				    _day   := (BYTE) dt:Day
                ELSE
                    _ymd := 0
                ENDIF
				RETURN

			/// Construct a date from another IDate type.
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			CONSTRUCTOR(d AS IDate)
                _ymd := 0
				_year  := (WORD) d:Year
				_month := (BYTE) d:Month
				_day   := (BYTE) d:Day
				RETURN

			/// Construct a date from a number of Ticks.
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			CONSTRUCTOR(ticks AS INT64)
				SELF(System.DateTime{ticks})
				RETURN

			/// Construct a date from a string. This assumes the string is in current Date format
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			CONSTRUCTOR(strDate AS STRING)
				LOCAL dValue := CToD(strDate) AS DATE
                _year := _month := _day := 0
				_ymd := dValue:_ymd

			/// Construct a date from a string using the specified Date Format.
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			CONSTRUCTOR(strDate AS STRING, strFormat AS STRING)
				LOCAL dValue := CToD(strDate,strFormat) AS DATE
                _year := _month := _day := 0
				_ymd := dValue:_ymd

			/// Construct a date from year, month, day
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			CONSTRUCTOR(year AS INT, month AS INT, day AS INT)
                _ymd := _year := _month := _day := 0
                IF year != 0 .AND. month != 0 .AND. day != 0
				    TRY
					    // this may throw an exception when the combination is not valid
                        IF (year == 1 .AND. month == 1 .AND. day == 1)
                            // this is DateTime.Mindate, but a valid date in VO
					        _year  := 1
					        _month := 1
					        _day   := 1
                        ELSE
  				            VAR dt := System.DateTime{year, month, day}
					        _year  := (WORD) dt:Year
					        _month := (BYTE) dt:Month
					        _day   := (BYTE) dt:Day
                        ENDIF
				    CATCH
        				_ymd := 0 // null_date
					    NOP
                     END TRY
                ELSE
				    _ymd := 0 // null_date
                ENDIF
				RETURN

			/// Construct a date from year, month, day
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			CONSTRUCTOR(year AS DWORD, month AS DWORD, day AS DWORD)
				// Chain to Int constructor
				SELF( (INT) year, (INT) month, (INT) day)
				RETURN

		#endregion

		#region methods
			/// <inheritdoc />
			METHOD CompareTo(o AS OBJECT) AS LONG
				VAR rhs := (DATE)o
				RETURN _ymd:CompareTo(rhs:_ymd)

			/// <inheritdoc />
			METHOD CompareTo(rhs AS DATE) AS LONG
				RETURN _ymd:CompareTo(rhs:_ymd)

			/// <inheritdoc />
			OVERRIDE METHOD GetHashCode() AS LONG
				RETURN _ymd:GetHashCode()

			/// <inheritdoc />
			METHOD GetTypeCode() AS System.TypeCode
				RETURN TypeCode.DateTime
		#endregion
		#region Operators
			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR ++(lhs AS DATE) AS DATE
				RETURN lhs:Add(1)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR +(lhs AS DATE, days AS USUAL) AS DATE
				RETURN lhs:Add(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR +(lhs AS DATE, days AS REAL8) AS DATE
				RETURN lhs:Add(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR +(lhs AS DATE, days AS LONG) AS DATE
				RETURN lhs:Add(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR +(lhs AS DATE, days AS INT64) AS DATE
				RETURN lhs:Add(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR +(lhs AS DATE, ts AS System.TimeSpan) AS DATE
				RETURN lhs:Add(ts)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR +(lhs AS DATE, days AS DWORD) AS DATE
				RETURN lhs:Add(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR +(lhs AS DATE, days AS UINT64) AS DATE
				RETURN lhs:Add(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR --(lhs AS DATE) AS DATE
				RETURN lhs:Subtract(1)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR -(lhs AS DATE, rhs AS DATE) AS LONG
				RETURN lhs:Subtract(rhs)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR -(lhs AS DATE, days AS USUAL) AS USUAL
				RETURN lhs:Subtract(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR -(lhs AS DATE, days AS REAL8) AS DATE
				RETURN lhs:Subtract(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR -(lhs AS DATE, days AS LONG) AS DATE
				RETURN lhs:Subtract(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR -(lhs AS DATE, days AS INT64) AS DATE
				RETURN lhs:Subtract(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR -(lhs AS DATE, ts AS System.TimeSpan) AS DATE
				RETURN lhs:Subtract(ts)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR -(lhs AS DATE, days AS DWORD) AS DATE
				RETURN lhs:Subtract(days)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			STATIC OPERATOR -(lhs AS DATE, days AS UINT64) AS DATE
				RETURN lhs:Subtract(days)

		#endregion
		#region Equality Operators

			/// <inheritdoc />
			METHOD Equals(lhs AS DATE) AS LOGIC
				RETURN _ymd == lhs:_ymd

			/// <inheritdoc />
			OVERRIDE METHOD Equals(o AS OBJECT) AS LOGIC
				IF o != NULL
					IF o:GetType() == TYPEOF(DATE)
						RETURN SELF:Equals(  (DATE) o)
					ELSEIF o:GetType() == TYPEOF(System.DateTime)
						RETURN SELF:Equals( DATE{ (System.DateTime) o})
					ENDIF
				ENDIF
			RETURN FALSE

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR ==(lhs AS DATE, rhs AS DATE) AS LOGIC
				RETURN (lhs:_ymd == rhs:_ymd)

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR !=(lhs AS DATE, rhs AS DATE) AS LOGIC
				RETURN (lhs:_ymd != rhs:_ymd)

		#endregion
		#region Implicit and Explicit converters

			/// <include file="RTComments.xml" path="Comments/Converter/*" />
			STATIC OPERATOR EXPLICIT(dvalue AS DATE) AS DWORD
				// convert to julian date like vo
				// # of days since 1-1-1901 + 2415386
				LOCAL nDays AS DWORD
				nDays := (DWORD) (dvalue:Value - _dtCalc):Days + CLIPPER_MIN_DATE
				RETURN nDays

			/// <include file="RTComments.xml" path="Comments/Converter/*" />
			STATIC OPERATOR EXPLICIT(dValue AS DATE) AS INT
				LOCAL nDays AS LONG
				nDays := (INT) ((dValue:Value - _dtCalc):Days + CLIPPER_MIN_DATE)
				RETURN nDays

			/// <include file="RTComments.xml" path="Comments/Converter/*" />
			STATIC OPERATOR EXPLICIT(dw AS DWORD ) AS DATE
				// convert julian date to our date
				LOCAL result := _NULL_DATE AS DATE
				IF dw >= CLIPPER_MIN_DATE .AND. dw <= CLIPPER_MAX_DATE
					LOCAL dt AS DateTime
					dt := _dtCalc:AddDays ( (INT) dw -CLIPPER_MIN_DATE)
					result := DATE{dt}
				ENDIF
				RETURN result

			/// <include file="RTComments.xml" path="Comments/Converter/*" />
			STATIC OPERATOR EXPLICIT(i AS INT) AS DATE
				LOCAL result := _NULL_DATE AS DATE
				IF i >= CLIPPER_MIN_DATE .AND. i <= CLIPPER_MAX_DATE
					LOCAL dt AS DateTime
					dt := _dtCalc:AddDays ( i - CLIPPER_MIN_DATE)
					result := DATE{dt}
				ENDIF
				RETURN result

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR IMPLICIT(v AS System.DateTime) AS DATE
				RETURN DATE{v}

			/// <exclude />
			METHOD ToDateTime() AS System.DateTime
				RETURN SELF:Value

			/// <exclude />
			METHOD FromDateTime(dtvalue AS System.DateTime) AS DATE
				RETURN DATE{dtvalue}

		#endregion
		#region Comparison Operators
			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR >(lhs AS DATE, rhs AS DATE) AS LOGIC
				// note: cannot use _ymd for > comparison because that would depend
				//       on the processor layout
				IF lhs:_ymd == rhs:_ymd
					RETURN FALSE
				ELSEIF lhs:_year > rhs:_year
					RETURN TRUE
				ELSEIF lhs:_year == rhs:_year
					IF lhs:_month > rhs:_month
						RETURN TRUE
					ELSEIF lhs:_month == rhs:_month .AND. lhs:_day > rhs:_day
						RETURN TRUE
					ENDIF
				ENDIF
				RETURN FALSE

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR >=(lhs AS DATE, rhs AS DATE) AS LOGIC
				// note: cannot use _ymd for > comparison because that would depend
				//       on the processor layout
				IF lhs:_ymd == rhs:_ymd
					RETURN TRUE
				ELSEIF lhs:_year > rhs:_year
					RETURN TRUE
				ELSEIF lhs:_year == rhs:_year
					IF lhs:_month > rhs:_month
						RETURN TRUE
					ELSEIF lhs:_month == rhs:_month .AND. lhs:_day >= rhs:_day
						RETURN TRUE
					ENDIF
				ENDIF
				RETURN FALSE

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR IMPLICIT(v AS DATE) AS System.DateTime
				RETURN v:Value

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR <(lhs AS DATE, rhs AS DATE) AS LOGIC
				// note: cannot use _ymd for < comparison because that would depend
				//       on the processor layout
				IF lhs:_ymd == rhs:_ymd
					RETURN FALSE
				ELSEIF lhs:_year < rhs:_year
					RETURN TRUE
				ELSEIF lhs:_year == rhs:_year
					IF lhs:_month < rhs:_month
						RETURN TRUE
					ELSEIF lhs:_month == rhs:_month .AND. lhs:_day < rhs:_day
						RETURN TRUE
					ENDIF
				ENDIF
				RETURN FALSE

			/// <include file="RTComments.xml" path="Comments/Operator/*" />
			STATIC OPERATOR <=(lhs AS DATE, rhs AS DATE) AS LOGIC
				// note: cannot use _ymd for < comparison because that would depend
				//       on the processor layout
				IF lhs:_ymd == rhs:_ymd
					RETURN TRUE
				ELSEIF lhs:_year < rhs:_year
					RETURN TRUE
				ELSEIF lhs:_year == rhs:_year
					IF lhs:_month < rhs:_month
						RETURN TRUE
					ELSEIF lhs:_month == rhs:_month .AND. lhs:_day <= rhs:_day
						RETURN TRUE
					ENDIF
				ENDIF
				RETURN FALSE
		#endregion

		#region ADD and Subtract Methods
			/// <exclude />
			METHOD Add(days AS USUAL) AS DATE
				IF days:IsInteger
					RETURN SELF:Add( (INT64) days)
				ELSEIF days:IsNumeric
					 RETURN SELF:Add( (REAL8) days)
				ELSE
					THROW Error.ArgumentError(__ENTITY__,NAMEOF(days),1, "Incompatible argument for Date:Add()",{days})
				ENDIF
			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Add(days AS REAL8) AS DATE
				VAR res := SELF:Value:AddDays(days)
				RETURN DATE{res}
			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Add(days AS LONG) AS DATE
				VAR res := SELF:Value:AddDays(days)
				RETURN DATE{res}
			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Add(days AS INT64) AS DATE
				VAR res := SELF:Value:AddDays(days)
				RETURN DATE{res}

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Add(span AS System.TimeSpan) AS DATE
				VAR res := SELF:Value:Add(span)
				RETURN DATE{res}

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Add(days AS DWORD) AS DATE
				VAR res := SELF:Value:AddDays(days)
				RETURN DATE{res}

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Add(days AS UINT64) AS DATE
				VAR res := SELF:Value:AddDays(days)
				RETURN DATE{res}

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Subtract(lhs AS DATE) AS LONG
				LOCAL span AS System.TimeSpan
				span := (System.TimeSpan)(SELF:Value - lhs:Value)
				RETURN span:Days

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Subtract(days AS USUAL) AS USUAL
				IF days:IsInteger
					RETURN SELF:Subtract( -(INT64) days)
				ELSEIF days:IsNumeric
					 RETURN SELF:Subtract( -(REAL8) days)
				ELSEIF days:IsDate
					 RETURN SELF:Subtract( (DATE) days)
				ELSE
					THROW Error.ArgumentError(__ENTITY__,NAMEOF(days), 1, "Incompatible argument for Date:Subtract()", {days})
				ENDIF

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Subtract(days AS REAL8) AS DATE
				RETURN SELF:Add(-days)

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Subtract(days AS LONG) AS DATE
				RETURN SELF:Add(-days)

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Subtract(days AS INT64) AS DATE
				RETURN SELF:Add(-days)

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Subtract(ts AS System.TimeSpan) AS DATE
				RETURN SELF:Add(-ts)

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Subtract(days AS DWORD) AS DATE
				RETURN SELF:Add(-days)

			/// <exclude />
            [MethodImpl(MethodImplOptions.AggressiveInlining)];
			METHOD Subtract(days AS UINT64) AS DATE
				RETURN SELF:Add(-(INT64)days)
		#endregion

		#region IConvertable
			// forward most methods to the DateTime class so there will
			// be a proper (localized) error message
			/// <inheritdoc />
			METHOD IConvertible.ToBoolean(provider AS System.IFormatProvider) AS LOGIC
				RETURN ((IConvertible)SELF:Value):ToBoolean(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToByte(provider AS System.IFormatProvider) AS BYTE
				RETURN ((IConvertible)SELF:Value):ToByte(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToChar(provider AS System.IFormatProvider) AS CHAR
				RETURN ((IConvertible)SELF:Value):ToChar(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToDateTime(provider AS System.IFormatProvider) AS System.DateTime
				RETURN SELF:Value

			/// <inheritdoc />
			METHOD IConvertible.ToDecimal(provider AS System.IFormatProvider) AS Decimal
				RETURN ((IConvertible)SELF:Value):ToDecimal(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToDouble(provider AS System.IFormatProvider) AS REAL8
				RETURN ((IConvertible)SELF:Value):ToDouble(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToInt16(provider AS System.IFormatProvider) AS SHORT
				RETURN ((IConvertible)SELF:Value):ToInt16(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToInt32(provider AS System.IFormatProvider) AS LONG
				RETURN ((IConvertible)SELF:Value):ToInt32(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToInt64(provider AS System.IFormatProvider) AS INT64
				RETURN ((IConvertible)SELF:Value):ToInt64(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToSByte(provider AS System.IFormatProvider) AS SByte
				RETURN ((IConvertible)SELF:Value):ToSByte(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToSingle(provider AS System.IFormatProvider) AS REAL4
				RETURN ((IConvertible)SELF:Value):ToSingle(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToType(conversionType AS System.Type, provider AS System.IFormatProvider) AS OBJECT
				IF conversionType == TYPEOF(DATE)
					RETURN SELF
                ELSEIF conversionType == TYPEOF(STRING)
                    RETURN SELF:ToString()
				ELSEIF conversionType == TYPEOF(System.DateTime)
					RETURN SELF:Value
				ENDIF
				RETURN ((IConvertible)SELF:Value):ToType(conversionType, provider)

			/// <inheritdoc />
			METHOD IConvertible.ToUInt16(provider AS System.IFormatProvider) AS WORD
				RETURN ((IConvertible)SELF:Value):ToUInt16(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToUInt32(provider AS System.IFormatProvider) AS DWORD
				RETURN ((IConvertible)SELF:Value):ToUInt32(provider)

			/// <inheritdoc />
			METHOD IConvertible.ToUInt64(provider AS System.IFormatProvider) AS UINT64
				RETURN ((IConvertible)SELF:Value):ToUInt64(provider)
		#endregion

		#region ToString()
			// Use DateTime ToString() methods as helpers
			/// <inheritdoc />
			OVERRIDE METHOD ToString() AS STRING
				IF (_ymd == 0)
					RETURN RuntimeState.NullDateString
				ENDIF
			    RETURN DToC(SELF)
			/// <inheritdoc />
			METHOD ToString(provider AS System.IFormatProvider) AS STRING
				IF (_ymd == 0)
					RETURN RuntimeState.NullDateString
				ENDIF
				RETURN SELF:Value:ToString(provider)
			/// <inheritdoc cref="M:System.DateTime.ToString(System.String)"/>
			METHOD ToString(s AS STRING) AS STRING
				IF (_ymd == 0)
					RETURN RuntimeState.NullDateString
				ENDIF
				RETURN SELF:Value:ToString(s)
			/// <inheritdoc />
			METHOD ToString(s AS STRING, fp AS System.IFormatProvider) AS STRING
				IF (_ymd == 0)
					RETURN RuntimeState.NullDateString
				ENDIF
				IF (s == NULL)
					s := XSharp.RuntimeState.GetValue<STRING>(Set.DateFormatNet)
				ENDIF
				RETURN SELF:Value:ToString(s, fp)
		#endregion

        #region ISerializable
        /// <inheritdoc/>
        PUBLIC METHOD GetObjectData(info AS SerializationInfo, context AS StreamingContext) AS VOID
            IF info == NULL
                THROW System.ArgumentException{"info"}
            ENDIF
            info:AddValue("Value", SELF:ToString())
            RETURN
        /// <include file="RTComments.xml" path="Comments/SerializeConstructor/*" />
        CONSTRUCTOR (info AS SerializationInfo, context AS StreamingContext)
            _ymd := _year := _month := _day := 0
            IF info == NULL
                THROW System.ArgumentException{"info"}
            ENDIF
            SELF := CToD(info:GetString("Value"))
        #endregion

        #region properties

			/// <inheritdoc />
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			PROPERTY IsEmpty AS LOGIC
				GET
					RETURN _ymd == 0
				END GET
			END PROPERTY
			/// <inheritdoc />
			PROPERTY Month	AS INT GET _month
			/// <inheritdoc />
			PROPERTY Year	AS INT GET _year
			/// <inheritdoc />
			PROPERTY Day	AS INT GET _day
			// Next properties for easy access in right type
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			INTERNAL PROPERTY DYear		AS DWORD GET _year
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			INTERNAL PROPERTY DMonth	AS DWORD GET _month
            [DebuggerBrowsable(DebuggerBrowsableState.Never)];
			INTERNAL PROPERTY DDay		AS DWORD GET _day

            INTERNAL METHOD ToDebugString() AS STRING
				IF (_ymd == 0)
					RETURN "NULL_DATE"
                ENDIF
                RETURN SELF:Value:ToShortDateString()

		#endregion

		#region STATIC Properties

			/// <exclude />
			STATIC METHOD NullDate AS DATE
				RETURN  _NULL_DATE

		#endregion

	END STRUCTURE
END NAMESPACE


add wikipedia link

add wikipedia link

-- See: http://en.wikipedia.org/wiki/Normalisation_by_evaluation
--
import "../src/LangPrelude.prg" (listM,Monad,compose,const)

monad listM

-- abstract syntax -------------------------------------------------------------

data Ty :: *0 ~> *0 where
   Bool :: Ty Bool
   Arr :: (Ty a) -> (Ty b) -> Ty (a -> b)

data Var :: *0 ~> *0 ~> *0 where
   ZVar :: Var ((h,t)) t
   SVar :: (Var h t) -> Var ((h,s)) t

data Exp :: *0 ~> *0 ~> *0 where
   Var :: (Var g t) -> Exp g t
   Lam :: (Ty a) -> (Exp ((g,a)) b) -> Exp g (a -> b)
   App :: (Exp g (s -> t)) -> (Exp g s) -> Exp g t
   If :: (Exp g Bool) -> (Exp g t) -> (Exp g t) -> Exp g t
   ETrue :: Exp g Bool
   EFalse :: Exp g Bool

-- smart constructors ----------------------------------------------------------
lamE :: Ty s -> (Exp (g,s) s -> Exp (g,s) t) -> Exp g (s -> t)
lamE s f = Lam s (f (Var ZVar))

ifE :: Exp g Bool -> Exp g t -> Exp g t -> Exp g t
ifE t ETrue EFalse = t
ifE t e e' = if eqE e e' then e else If t e e'

-- boring equality tests -------------------------------------------------------
eqB :: BoxExp t -> BoxExp s -> Bool
eqB (Box e) (Box e_) = eqE e e_

eqE :: Exp g t -> Exp h s -> Bool
eqE (Var x) (Var y) = eqV x y
eqE (Lam s e) (Lam s_ e_) = eqT s s_ && eqE e e_
eqE (App e1 e2) (App e1_ e2_) = eqE e1 e1_ && eqE e2 e2_
eqE (If e1 e2 e3) (If e1_ e2_ e3_) = eqE e1 e1_ && (eqE e2 e2_ && eqE e3 e3_)
eqE (ETrue) (ETrue) = True
eqE (EFalse) (EFalse) = True
eqE _ _ = False

eqT :: Ty t -> Ty s -> Bool
eqT (Arr s t) (Arr s_ t_) = eqT s s_ && eqT t t_
eqT Bool Bool = True
eqT _ _ = False

eqV :: Var g t -> Var h s -> Bool
eqV (SVar x) (SVar y) = eqV x y
eqV ZVar ZVar = True
eqV _ _ = False

-- evaluation ------------------------------------------------------------------
var :: Var g t -> g -> t
var ZVar     (_,t) = t
var (SVar x) (h,s) = var x h

exp :: Exp g t -> g -> t
exp (Var x)    g = var x g
exp (Lam _ e)  g = \a -> exp e (g,a)
exp (App e e') g = exp e g (exp e' g)
exp (ETrue)    g = True
exp (EFalse)   g = False
exp (If c t e) g = if exp c g then exp t g else exp e g

-- type inference --------------------------------------------------------------

data TyEnv :: *0 ~> *0 where
   Cons :: (Ty t) -> (TyEnv h) -> TyEnv ((h,t))
   Nil :: TyEnv g

infer :: TyEnv g -> Exp g t -> Ty t
infer g (Var x)         = inferVar g x
infer g (Lam t e)       = Arr t (infer (Cons t g) e)
infer g (App e e')      = case infer g e of Arr _ t -> t
infer g (ETrue)         = Bool
infer g (EFalse)        = Bool
infer g (If _ e _)      = infer g e

inferVar :: TyEnv g -> Var g t -> Ty t
inferVar (Cons t h) (SVar x) = inferVar h x
inferVar (Cons t h) (ZVar)   = t

-- tree monad ------------------------------------------------------------------

data Tree a = Val a | Choice (Tree a) (Tree a)
-- doesn't yet force trees to be fully balanced:
--      Val :: a -> Tree a Z
--      Choice :: Tree a n -> Tree a n -> Tree a (S n)

treeM = Monad Val bind error
  where bind (Val a) f = f a
        bind (Choice l r) f = Choice (bind l f) (bind r f)

tmap f x = do a <- x; return (f a)
  where monad treeM

flatten t = flatten_ t []
 where
   flatten_ (Val a)      k = a:k
   flatten_ (Choice l r) k = flatten_ l (flatten_ r k)

-- quote & friends -------------------------------------------------------------

-- for values --------------------------
enumV           :: Ty t -> Tree t
questionsV      :: Ty t -> [t -> Bool]

enumV Bool      = Choice (Val True) (Val False)
enumV (Arr s t) = mkEnum (questionsV s) (enumV t)
 where
   monad treeM
   mkEnum [] t = tmap const t
   mkEnum (q:qs) es = do
                   f1 <- mkEnum qs es
                   f2 <- mkEnum qs es
                   return (\d -> if q d then f1 d else f2 d)

questionsV Bool         = return (\x -> x)
questionsV (Arr s t)    = do
                          d <- flatten (enumV s)
                          q <- questionsV t
                          return (\f -> q (f d))

-- for expressions ---------------------
enumE           :: Ty t -> Tree (Exp g t)
questionsE      :: Ty t -> [Exp g t -> Exp g Bool]

enumE Bool      = Choice (Val ETrue) (Val EFalse)
enumE (Arr s t) = tmap (lamE s) (mkEnumE (questionsE s) (enumE t))
 where
   monad treeM
   mkEnumE [] t = tmap const t
   mkEnumE (q:qs) es = do
                        f1 <- mkEnumE qs es
                        f2 <- mkEnumE qs es
                        return (\d -> ifE (q d) (f1 d) (f2 d))

questionsE Bool         = return (\x -> x)
questionsE (Arr s t)    = do
                          d <- flatten (enumE s)
                          q <- questionsE t
                          return (\f -> q (App f d))

-- should be
--      find (List (Exp g Bool) n) -> Tree (Exp g a) n -> Exp g a
find :: [Exp g Bool] -> Tree (Exp g a) -> Exp g a
find []         (Val a)         = a
find (b:bs)     (Choice l r)    = ifE b (find bs l) (find bs r)
find _          _               = error "bad arguments to find"

quote :: Ty t -> t -> Exp g t
quote Bool      t = case t of True -> ETrue; False -> EFalse
quote (Arr s t) f = lamE s (\e -> find (do q <- questionsE s; return (q e))
                                        (tmap (quote t . f) (enumV s)))

-- normalization (by evaluation) -----------------------------------------------
data BoxExp t = Box (forall g. Exp g t)

normalize :: Ty t -> BoxExp t -> BoxExp t
normalize s (Box e) = Box (quote s (exp e ()))

-- examples --------------------------------------------------------------------
b2b = Arr Bool Bool
b22b = Arr b2b b2b
zero = Var ZVar
one = Var (SVar ZVar)
once   = Box (Lam b2b (Lam Bool (App one zero)))
twice  = Box (Lam b2b (Lam Bool (App one (App one zero))))
thrice = Box (Lam b2b (Lam Bool (App one (App one (App one zero)))))

test = [ eqB (nf b22b thrice) (nf b22b once)
       , eqB (nf b22b twice)  (nf b22b once)]
  where nf = normalize


tests/QtWebSockets/websocketservermt.prg: updated (mutex used internally)

Modified:
tests/QtWebSockets/websocketservermt.prg

/*

  Qt5xHb Project - Test Program

  Copyright (C) 2018 Marcos Antonio Gambeta

  E-mail:
  marcosgambeta AT outlook DOT com

  Website:
  https://github.com/marcosgambeta/Qt5xHb

*/

#include "qt5xhb.ch"
#include "hbthread.ch"

//STATIC s_mutex := hb_mutexCreate()

PROCEDURE Main ()

   LOCAL oApp
   LOCAL oWebSocketServer

//   ? s_mutex

   oApp := QCoreApplication():new()

   oWebSocketServer := QWebSocketServer():new("WebSocketServer", QWebSocketServer_NonSecureMode)

   IF oWebSocketServer:listen(QHostAddress():new("127.0.0.1"), 1234)
      ? "servidor ativo na porta 1234"
   ELSE
      ? "servidor inativo"
      WAIT
      oWebSocketServer:delete()
      oApp:delete()
      QUIT
   ENDIF

   oWebSocketServer:onNewConnection( {||hb_ThreadStart( HB_BITOR( HB_THREAD_INHERIT_PUBLIC, HB_THREAD_MEMVARS_COPY ), {| oWebSocketServer | newConnection( oWebSocketServer ) }, oWebSocketServer )} )
   oWebSocketServer:onClosed( {||oApp:quit()} )

   oApp:exec()

   oWebSocketServer:delete()

   oSocket:delete()

   oApp:delete()

RETURN

STATIC FUNCTION newConnection (oWebSocketServer)

   LOCAL oSocket

   ? "entrando - newConnection"

//   hb_mutexLock( s_mutex )

   oSocket := oWebSocketServer:nextPendingConnection()

   oSocket:onDestroyed( {|oSender|qout(oSender:pointer),qout("socket destruido"),oSender:disconnectAll(.T.)} )
   oSocket:onTextMessageReceived( {|oSender, cText|qout(oSender:pointer),qout("textMessageReceived="+cText),oSender:sendTextMessage(cText)} )
   oSocket:onDisconnected( {|oSender|qout(oSender:pointer),qout("socket desconectado"),oSender:delete()} )

//   hb_mutexUnlock( s_mutex )

   ? "saindo - newConnection"

RETURN NIL

/*

  Qt5xHb Project - Test Program

  Copyright (C) 2018 Marcos Antonio Gambeta

  E-mail:
  marcosgambeta AT outlook DOT com

  Website:
  https://github.com/marcosgambeta/Qt5xHb

*/

#include "qt5xhb.ch"
#include "hbthread.ch"

STATIC s_mutex := hb_mutexCreate()

PROCEDURE Main ()

   LOCAL oApp
   LOCAL oWebSocketServer

   ? s_mutex

   oApp := QCoreApplication():new()

   oWebSocketServer := QWebSocketServer():new("WebSocketServer", QWebSocketServer_NonSecureMode)

   IF oWebSocketServer:listen(QHostAddress():new("127.0.0.1"), 1234)
      ? "servidor ativo na porta 1234"
   ELSE
      ? "servidor inativo"
      WAIT
      oWebSocketServer:delete()
      oApp:delete()
      QUIT
   ENDIF

   oWebSocketServer:onNewConnection( {||hb_ThreadStart( HB_BITOR( HB_THREAD_INHERIT_PUBLIC, HB_THREAD_MEMVARS_COPY ), {| oWebSocketServer | newConnection( oWebSocketServer ) }, oWebSocketServer )} )
   oWebSocketServer:onClosed( {||oApp:quit()} )

   oApp:exec()

   oWebSocketServer:delete()

   oSocket:delete()

   oApp:delete()

RETURN

STATIC FUNCTION newConnection (oWebSocketServer)

   LOCAL oSocket

   ? "iniciando nova conexao"

   hb_mutexLock( s_mutex )

   oSocket := oWebSocketServer:nextPendingConnection()

   oSocket:onDestroyed( {|oSender|qout(oSender:pointer),qout("socket destruido"),oSender:disconnectAll(.T.)} )
   oSocket:onTextMessageReceived( {|oSender, cText|qout(oSender:pointer),qout("textMessageReceived="+cText),oSender:sendTextMessage(cText)} )
   oSocket:onDisconnected( {|oSender|qout(oSender:pointer),qout("socket desconectado"),oSender:delete()} )

   hb_mutexUnlock( s_mutex )

   ? "finalizando nova conexao"

RETURN NIL