Namespace System


Class DateTime
	m_Date As Int64
Public
	Static MaxValue = 3162240000000000000 As Int64 'Const TicksPerDay*366*10000
	Static MinValue = 316224000000000 As Int64     'Const TicksPerDay*366

	Sub DateTime()
		initialize(MinValue, DateTimeKind.Unspecified)
	End Sub

	Sub DateTime(ticks As Int64)
		initialize(ticks, DateTimeKind.Unspecified)
	End Sub

	Sub DateTime(ticks As Int64, kind As DateTimeKind)
		initialize(ticks, kind)
	End Sub

	Sub DateTime(year As Long, month As Long, day As Long)
		initialize(year, month, day, 0, 0, 0, 0, DateTimeKind.Unspecified)
	End Sub

	Sub DateTime(year As Long, month As Long, day As Long, kind As DateTimeKind)
		initialize(year, month, day, 0, 0, 0, 0, kind)
	End Sub

	Sub DateTime(year As Long, month As Long, day As Long, hour As Long, minute As Long, second As Long)
		initialize(year, month, day, hour, minute, second, 0, DateTimeKind.Unspecified)
	End Sub

	Sub DateTime(year As Long, month As Long, day As Long, hour As Long, minute As Long, second As Long, kind As DateTimeKind)
		initialize(year, month, day, hour, minute, second, 0, kind)
	End Sub

	Sub DateTime(year As Long, month As Long, day As Long, hour As Long, minute As Long, second As Long, millisecond As Long)
		initialize(year, month, day, hour, minute, second, millisecond, DateTimeKind.Unspecified)
	End Sub

	Sub DateTime(year As Long, month As Long, day As Long, hour As Long, minute As Long, second As Long, millisecond As Long, kind As DateTimeKind)
		initialize(year, month, day, hour, minute, second, millisecond, kind)
	End Sub

	Sub DateTime(ByRef time As SYSTEMTIME)
		initialize(time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond, time.wMilliseconds, DateTimeKind.Unspecified)
	End Sub

	Sub DateTime(ByRef time As SYSTEMTIME, kind As DateTimeKind)
		initialize(time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond, time.wMilliseconds, kind)
	End Sub

	'Copy Constructor
	Sub DateTime(dateTime As DateTime)
		This.m_Date = dateTime.m_Date
	End Sub

	Sub ~DateTime()
	End Sub

	Function Operator + (value As TimeSpan) As DateTime
		Return New DateTime(Ticks + value.Ticks)
	End Function

	Function Operator - (value As DateTime) As TimeSpan
		Return TimeSpan.FromTicks(Ticks - value.Ticks)
	End Function

	Function Operator - (value As TimeSpan) As DateTime
		Return New DateTime(Ticks - value.Ticks)
	End Function

	Function Operator == (value As DateTime) As Boolean
		Return Equals(value)
	End Function

	Function Operator <> (value As DateTime) As Boolean
		Return Not Equals(value)
	End Function

	Function Operator > (value As DateTime) As Boolean
		If DateTime.Compare(This, value) > 0 Then
			Return True
		Else
			Return False
		End If
	End Function

	Function Operator < (value As DateTime) As Boolean
		If DateTime.Compare(This, value) < 0 Then
			Return True
		Else
			Return False
		End If
	End Function

	Function Operator >= (value As DateTime) As Boolean
		If DateTime.Compare(This, value) => 0 Then
			Return True
		Else
			Return False
		End If
	End Function

	Function Operator <= (value As DateTime) As Boolean
		If DateTime.Compare(This, value) <= 0 Then
			Return True
		Else
			Return False
		End If
	End Function
	
	'Public Properties
	Function Ticks() As Int64
		Return (m_Date And &H3FFFFFFFFFFFFFFF)
	End Function

	Function Millisecond() As Long
		Return (Ticks \ TimeSpan.TicksPerMillisecond Mod 1000) As Long
	End Function

	Function Second() As Long
		Return (Ticks \ TimeSpan.TicksPerSecond Mod 60) As Long
	End Function

	Function Minute() As Long
		Return (Ticks \ TimeSpan.TicksPerMinute Mod 60) As Long
	End Function

	Function Hour() As Long
		Return (Ticks \ TimeSpan.TicksPerHour Mod 24) As Long
	End Function

	Function Day() As Long
		Return DayOfYear - totalDaysOfMonth(Year, Month - 1)
	End Function

	Function Month() As Long
		Dim year = Year As Long
		Dim day = DayOfYear As Long
		Dim i = 1 As Long
		While day > totalDaysOfMonth(year, i)
			i++
		Wend
		Return i
	End Function

	Function Year() As Long
		Dim day = (Ticks \ TimeSpan.TicksPerDay) As Long
		Dim year = Int((day + day \ 36524 - day \ 146097) / 365.25) + 1 As Long
		If day - yearToDay(year - 1) + 1 = 366 Then
			Return year + 1
		Else
			Return year
		End If
	End Function

	Function DayOfWeek() As Long 'As DayOfWeek
		Return ((Ticks \ TimeSpan.TicksPerDay) Mod 7) As Long + 1
	End Function

	Function Kind() As DateTimeKind
		Return kindFromBinary(m_Date)
	End Function

	Function DayOfYear() As Long
		Return ((Ticks \ TimeSpan.TicksPerDay) - yearToDay(Year) + 1) As Long
	End Function

	Function Date() As DateTime
		Return New DateTime(Year, Month, Day, Kind)
	End Function

	Static Function Now() As DateTime
		Dim time As SYSTEMTIME
		GetLocalTime(time)
		Return New DateTime(time, DateTimeKind.Local)
	End Function

	Static Function Today() As DateTime
		Dim time As SYSTEMTIME
		GetLocalTime(time)
		Return New DateTime(time.wYear, time.wMonth, time.wDay, DateTimeKind.Local)
	End Function

	Static Function UtcNow() As DateTime
		Dim time As SYSTEMTIME
		GetSystemTime(time)
		Return New DateTime(time, DateTimeKind.Utc)
	End Function
		
	'Public Methods
	Static Function Compare(t1 As DateTime, t2 As DateTime) As Int64
		Return t1.Ticks - t2.Ticks
	End Function

	Function Equals(value As DateTime) As Boolean
		If value.m_Date = m_Date Then
			Return True
		Else
			Return False
		End If
	End Function

	Static Function Equals(t1 As DateTime, t2 As DateTime) As Boolean
		If t1.m_Date = t2.m_Date Then
			Return True
		Else
			Return False
		End If
	End Function

	Override Function GetHashCode() As Long
		Return HIDWORD(m_Date) Xor LODWORD(m_Date)
	End Function

	Function Add(value As TimeSpan) As DateTime
		Return This + value
	End Function

	Function AddTicks(value As Int64) As DateTime
		Return New DateTime(Ticks + value, Kind )
	End Function

	Function AddMilliseconds(value As Double) As DateTime
		Return AddTicks((value * TimeSpan.TicksPerMillisecond) As Int64)
	End Function

	Function AddSeconds(value As Double) As DateTime
		Return AddTicks((value * TimeSpan.TicksPerSecond) As Int64)
	End Function

	Function AddMinutes(value As Double) As DateTime
		Return AddTicks((value * TimeSpan.TicksPerMinute) As Int64)
	End Function

	Function AddHours(value As Double) As DateTime
		Return AddTicks((value * TimeSpan.TicksPerHour) As Int64)
	End Function

	Function AddDays(value As Double) As DateTime
		Return AddTicks((value * TimeSpan.TicksPerDay) As Int64)
	End Function

	Function AddYears(value As Double) As DateTime
		Dim date = New DateTime(Year + Int(value), Month, Day, Hour, Minute, Second, Millisecond, Kind)
		Dim ticks = Ticks _
				- (yearToDay(Year) + totalDaysOfMonth(Year, Month - 1) + Day - 1) * TimeSpan.TicksPerDay _
				- Hour * TimeSpan.TicksPerHour _
				- Minute * TimeSpan.TicksPerMinute _
				- Second * TimeSpan.TicksPerSecond _
				- Millisecond * TimeSpan.TicksPerMillisecond As Int64
		If IsLeapYear(Year + Int(value)) Then
			ticks += (value - Int(value)) * 366 * TimeSpan.TicksPerDay
		Else
			ticks += (value - Int(value)) * 365 * TimeSpan.TicksPerDay
		End If
		Return date.AddTicks(ticks)
	End Function

	Function Subtract(value As DateTime) As TimeSpan
		Return This - value
	End Function

	Function Subtract(value As TimeSpan) As DateTime
		Return This - value
	End Function

	Static Function DaysInMonth(year As Long, month As Long) As Long
		If year < 1 Or year > 9999 Or month < 1 Or month > 12 Then
			'ArgumentOutOfRangeException
			debug
		End If

		If IsLeapYear(year) And month = 2 Then
			Return 29
		Else
			Dim daysInMonth[11] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] As Byte
			Return daysInMonth[month - 1]
		End If
	End Function

	Static Function IsLeapYear(year As Long) As Boolean
		If (year Mod 400) = 0 Then Return True
		If (year Mod 100) = 0 Then Return False
		If (year Mod 4) = 0 Then Return True
		Return False
	End Function

	Function GetDateTimeFormats() As String
		Dim time = getSystemTime() 'As SYSTEMTIME を記述すると内部エラーが発生
		Dim dateFormatSize = GetDateFormat(LOCALE_USER_DEFAULT, 0, time, NULL, NULL, 0)
		Dim timeFormatSize = GetTimeFormat(LOCALE_USER_DEFAULT, 0, time, NULL, NULL, 0)
		Dim strLength = dateFormatSize + timeFormatSize
		Dim dateTimeFormats = GC_malloc_atomic(SizeOf (TCHAR) * (strLength)) As PTSTR
		GetDateFormat(LOCALE_USER_DEFAULT, 0, time, NULL, dateTimeFormats, dateFormatSize)
		dateTimeFormats[dateFormatSize - 1] = Asc(" ")
		GetTimeFormat(LOCALE_USER_DEFAULT, 0, time, NULL, dateTimeFormats + dateFormatSize, timeFormatSize)
'Debug
		Return New String(dateTimeFormats, strLength)
	End Function

	Function GetDateTimeFormats(format As *TCHAR) As String
		Dim time = getSystemTime() 'As SYSTEMTIME を記述すると内部エラーが発生
		Dim dateFormatSize = GetDateFormat(LOCALE_USER_DEFAULT, 0, time, format, NULL, 0)
		Dim dateFormats = malloc(dateFormatSize) As PTSTR
		GetDateFormat(LOCALE_USER_DEFAULT, 0, time, format, dateFormats, dateFormatSize)

		Dim dateTimeFormatSize = GetTimeFormat(LOCALE_USER_DEFAULT, 0, time, dateFormats, NULL, 0)
		Dim dateTimeFormats = malloc(dateTimeFormatSize) As PTSTR
		GetTimeFormat(LOCALE_USER_DEFAULT, 0, time, dateFormats, dateTimeFormats, dateTimeFormatSize)

		Return New String(dateTimeFormats)
	End Function

	Static Function FromBinary(date As Int64) As DateTime
		Return New DateTime((date And &H3FFFFFFFFFFFFFFF), kindFromBinary(date))
	End Function

	Function ToBinary() As Int64
		Return m_Date
	End Function

	Static Function FromFileTime(ByRef fileTime As FILETIME) As DateTime
		Dim localTime As FILETIME
		Dim time As SYSTEMTIME
		FileTimeToLocalFileTime(fileTime, localTime)
		FileTimeToSystemTime(localTime, time)
		Return New DateTime(time, DateTimeKind.Local)
	End Function

	Function ToFileTime() As FILETIME
		Dim time = getSystemTime() 'As SYSTEMTIME を記述すると内部エラーが発生 rev.207
		Dim fileTime As FILETIME
		SystemTimeToFileTime(time, fileTime)
		Return fileTime
	End Function

	Static Function FromFileTimeUtc(ByRef fileTime As FILETIME) As DateTime
		Dim time As SYSTEMTIME
		FileTimeToSystemTime(fileTime, time)
		Return New DateTime(time, DateTimeKind.Utc)
	End Function

	Function ToFileTimeUtc() As FILETIME
		Dim fileTime = ToFileTime() 'As FILETIME を記述すると内部エラー rev.207
		If Kind = DateTimeKind.Utc Then
			Return fileTime
		Else
			Dim utcTime As FILETIME
			LocalFileTimeToFileTime(fileTime, utcTime)
			Return utcTime
		End If
	End Function

	Function ToLocalTime() As DateTime
		If Kind = DateTimeKind.Local Then
			Return New DateTime(This)
		Else
			Dim fileTime = ToFileTime() '直接入れると計算できなくなります。 rev.207
			Return DateTime.FromFileTime(fileTime)
		End If
	End Function

	Override Function ToString() As String
		Return GetDateTimeFormats()
	End Function

	Function ToUniversalTime() As DateTime
		If Kind = DateTimeKind.Utc Then
			Return New DateTime(m_Date)
		Else
			Dim fileTime = ToFileTimeUtc() '直接入れると計算できなくなります。 rev.207
			Return DateTime.FromFileTimeUtc(fileTime)
		End If
	End Function

Private
	Sub initialize(ticks As Int64, kind As DateTimeKind)
		Kind = kind
		Ticks = ticks
	End Sub

	Sub initialize(year As Long, month As Long, day As Long, hour As Long, minute As Long, second As Long, millisecond As Long, kind As DateTimeKind)
		If month < 1 Or month > 12 _
			Or day < 1 Or day > DaysInMonth(year, month) _
			Or hour < 0 Or hour => 24 _
			Or minute < 0 Or minute => 60 _
			Or second < 0 Or second => 60 _
			Or millisecond < 0 Or millisecond  => 1000 Then
			debug 'ArgumentOutOfRangeException
		End If

		initialize(
			yearToDay(year) * TimeSpan.TicksPerDay _
			+ totalDaysOfMonth(year, month - 1) * TimeSpan.TicksPerDay _
			+ (day - 1) * TimeSpan.TicksPerDay _
			+ hour * TimeSpan.TicksPerHour _
			+ minute * TimeSpan.TicksPerMinute _
			+ second * TimeSpan.TicksPerSecond _
			+ millisecond * TimeSpan.TicksPerMillisecond,
			kind
			)
	End Sub

	Sub Ticks(value As Int64)
		If value < MinValue Or value > MaxValue Then
			debug 'ArgumentOutOfRangeException
		End If

		Dim temp = Kind As DateTimeKind
		m_Date = value
		Kind = temp
	End Sub

	Sub Kind(kind As DateTimeKind)
		Dim temp As Int64
		temp = kind
		temp = (temp << 62) And &HC000000000000000
		m_Date = (m_Date And &H3FFFFFFFFFFFFFFF) Or temp
	End Sub

	Static Function kindFromBinary(date As Int64) As DateTimeKind
		date = (date >> 62) And &H03
		If date = &H01 Then
			Return DateTimeKind.Local
		ElseIf date = &H02 Then
			Return DateTimeKind.Unspecified
		ElseIf date = &H03 Then
			Return DateTimeKind.Utc
		End If

		' ここにはこないはず
		debug
	End Function

	Function getSystemTime() As SYSTEMTIME
		Dim time As SYSTEMTIME
		With time
			.wYear = Year As Word
			.wMonth = Month As Word
			.wDayOfWeek = DayOfWeek As Word
			.wDay = Day As Word
			.wHour = Hour As Word
			.wMinute = Minute As Word
			.wSecond = Second As Word
			.wMilliseconds = Millisecond As Word
		End With
		Return time
	End Function

	Static Function yearToDay(year As Long) As Long
		year--
		Return (Int(year * 365.25) - Int(year * 0.01) + Int(year * 0.0025))
	End Function

	Static Function totalDaysOfMonth(year As Long, month As Long) As Long
		Dim days As Long
		Dim i As Long
		For i = 1 To month
			days += DaysInMonth(year, i)
		Next
		Return days
	End Function
End Class

Enum DateTimeKind
	Local
	Unspecified
	Utc
End Enum

Enum DayOfWeek
	Sunday = 0
	Monday
	Tuesday
	Wednesday
	Thursday
	Friday
	Saturday
End Enum


End Namespace
