Some date functions supporting also dates prior to 1970
Posted: Sat Aug 17, 2013 5:45 am
Please don't blame me for using ASM. I know it can easily be done without but that's just the fun part for me
You can use these procedures if you need to support dates prior to 1970 or if you need the speed as they same to be faster compared to the built in routines.
Dates have to be between 1582/10/15 (-12219292800) and 9999/12/31 (253402300799)
Supported procedures :
DateQ(year, month, day, hour, min, sec)
DateQInfo(*Info.DateQInfo, DateQ.q)
AddDateQ(DateQ.q, Type, Value)
DayFromYMW(Year, Month, Weekday, n)
DayOfWeekQ(DateQ.q)
DayOfYearQ(DateQ.q)
DayQ(DateQ.q)
DaysInMonth(DateQ.q)
FormatDateQ(Mask$, DateQ.q)
HourQ(DateQ.q)
IsLeapYear(Year)
MinuteQ(DateQ.q)
MonthQ(DateQ.q)
SecondQ(DateQ.q)
WeekISO(DateQ.q)
YearQ(DateQ.q)

You can use these procedures if you need to support dates prior to 1970 or if you need the speed as they same to be faster compared to the built in routines.
Dates have to be between 1582/10/15 (-12219292800) and 9999/12/31 (253402300799)
Supported procedures :
DateQ(year, month, day, hour, min, sec)
DateQInfo(*Info.DateQInfo, DateQ.q)
AddDateQ(DateQ.q, Type, Value)
DayFromYMW(Year, Month, Weekday, n)
DayOfWeekQ(DateQ.q)
DayOfYearQ(DateQ.q)
DayQ(DateQ.q)
DaysInMonth(DateQ.q)
FormatDateQ(Mask$, DateQ.q)
HourQ(DateQ.q)
IsLeapYear(Year)
MinuteQ(DateQ.q)
MonthQ(DateQ.q)
SecondQ(DateQ.q)
WeekISO(DateQ.q)
YearQ(DateQ.q)
Code: Select all
; *** DateQ.pbi v 1.05 2016/08/24 ***
; *** Structures ***
Structure DateQInfo
yday.w ; offset 0 : days since Jan 1 [1-366]
year.w ; offset 2 : year [1600-9999]
month.b ; offset 4 : months [1-12]
day.b ; offset 5 : day of the month [1-31]
hour.b ; offset 6 : hours [0-23]
min.b ; offset 7 : minutes [0-59]
sec.b ; offset 8 : seconds [0-59]
wday.b ; offset 9 : days since Sunday [0-6]
EndStructure
Structure DateQ_CharArray
c.c[0]
EndStructure
; *** Create lookup tables (about 65 KiB) ***
Global Dim DateQ_LUT.l(16384)
Global Dim DateQ_LUT2.w(2024)
Procedure DateQ_LUT_Init()
Protected.i y, y_, d, d_, w, m
While y < 16384
d = y >> 2 : y_ = d / 25 : d - y_ : y_ >> 2 : d + y_
y + 1
y_ = d - d_ : w + 1 + y_ : d_ = d
If w > 6 : w - 7 : EndIf
DateQ_LUT(y - 1) | y_ << 4
DateQ_LUT(y) = (d + y * 365 + 1) << 8 | w << 5
Wend
y = 0 : y_ = 512
For m = 1 To 12
If m = 2
For d = 1 To 28
w = m | d << 8
DateQ_LUT2(y) = w : y + 1
DateQ_LUT2(y_) = w : y_ + 1
Next
DateQ_LUT2(y_) = 2 | 29 << 8 : y_ + 1
Else
d_ = 30 + (m & 1) ! (m >> 3)
For d = 1 To d_
w = m | d << 8
DateQ_LUT2(y) = w : y + 1
DateQ_LUT2(y_) = w : y_ + 1
Next
EndIf
Next
EndProcedure
DateQ_LUT_Init()
; *** ASM procedures ***
Procedure.q DateQ(year, month = 1, day = 1, hour = 0, min = 0, sec = 0)
!mov ecx, [p.v_year]
!and ecx, 16383
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!mov rdx, [a_DateQ_LUT]
!mov eax, [rdx + rcx * 4]
!mov rcx, [p.v_month]
!or rcx, rax
!and rcx, 31
!lea rdx, [dateq_oday_table]
!movsx rcx, word [rdx + rcx * 2]
!shr rax, 8
!add rax, rcx
!add rax, [p.v_day]
!sub rax, 0xafaa9
!imul rax, 86400
!mov rcx, [p.v_hour]
!imul rcx, 60
!add rcx, [p.v_min]
!imul rcx, 60
!add rcx, [p.v_sec]
!add rax, rcx
CompilerElse
!mov edx, [a_DateQ_LUT]
!mov eax, [edx + ecx * 4]
!mov ecx, [p.v_month]
!or ecx, eax
!and ecx, 31
!movsx ecx, word [dateq_oday_table + ecx * 2]
!shr eax, 8
!add eax, ecx
!add eax, [p.v_day]
!sub eax, 0xafaa9
!mov ecx, 86400
!imul ecx
!mov ecx, [p.v_hour]
!imul ecx, 60
!add ecx, [p.v_min]
!imul ecx, 60
!add ecx, [p.v_sec]
!add eax, ecx
!adc edx, 0
!sar ecx, 31
!add edx, ecx
CompilerEndIf
ProcedureReturn
!dateq_oday_table:
!dw -31,0,31,59,90,120,151,181,212,243,273,304,334,365,396,-61 ; normal year
!dw -31,0,31,60,91,121,152,182,213,244,274,305,335,366,397,-61 ; leap year
EndProcedure
Procedure DateQInfo(*Info.DateQInfo, DateQ.q)
!mov eax, [p.v_DateQ]
!mov edx, [p.v_DateQ + 4]
; split into date and time
!add eax, 0x79747c00
!adc edx, 0xe
!and edx, 0x7f
!mov ecx, 86400
!div ecx
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!mov r8, [p.p_Info]
!mov r9, rax
; process the time part
!mov eax, edx
!xor edx, edx
!mov ecx, 60
!div ecx
!mov [r8 + 8], dl
!div cl
!mov [r8 + 6], ax
; process the date part
!mov rax, r9
; guess the year
!mov ecx, 0xb36d83
!mul ecx
!and rdx, 16383
; correct it if wrong
!mov rcx, [a_DateQ_LUT]
!mov eax, [rcx + rdx * 4 + 4]
!shr rax, 8
!cmp r9, rax
!cmc
!adc rdx, 0
!mov [r8 + 2], dx; year
!mov eax, [rcx + rdx * 4]
; get the other info
!movzx rcx, al
!shr rax, 8
!sub r9, rax
!and r9, 511
!lea rdx, [r9 + 1]
!mov [r8], dx; yday
!mov rdx, rcx
!shl rdx, 5
!and rdx, 512
!or rdx, r9
!mov rax, [a_DateQ_LUT2]
!mov dx, [rax + rdx * 2]
!mov [r8 + 4], dx; day and month
!mov rax, r9
!shr rcx, 5
!add rax, rcx
!mov cl, 7
!div cl
!mov al, ah
!mov [r8 + 9], al; wday
CompilerElse
!mov ecx, [p.p_Info]
!push edi
!push ebx
!mov edi, ecx
!mov ebx, eax
; process the time part
!mov eax, edx
!xor edx, edx
!mov ecx, 60
!div ecx
!mov [edi + 8], dl
!div cl
!mov [edi + 6], ax
; process the date part
!mov eax, ebx
; guess the year
!mov ecx, 0xb36d83
!mul ecx
!and edx, 16383
; correct it if wrong
!mov ecx, [a_DateQ_LUT]
!mov eax, [ecx + edx * 4 + 4]
!shr eax, 8
!cmp ebx, eax
!cmc
!adc edx, 0
!mov [edi + 2], dx; year
!mov eax, [ecx + edx * 4]
; get the other info
!movzx ecx, al
!shr eax, 8
!sub ebx, eax
!and ebx, 511
!lea edx, [ebx + 1]
!mov [edi], dx; yday
!mov edx, ecx
!shl edx, 5
!and edx, 512
!or edx, ebx
!mov eax, [a_DateQ_LUT2]
!mov dx, [eax + edx * 2]
!mov [edi + 4], dx; day and month
!mov eax, ebx
!shr ecx, 5
!add eax, ecx
!mov cl, 7
!div cl
!mov [edi + 9], ah; wday
!pop ebx
!pop edi
CompilerEndIf
EndProcedure
; *** Non ASM procedures ***
Procedure IsLeapYear(Year)
ProcedureReturn DateQ_LUT(Year) >> 4 & 1
EndProcedure
Procedure YearQ(DateQ.q)
Protected I.DateQInfo : DateQInfo(@I, DateQ) : ProcedureReturn I\year
EndProcedure
Procedure MonthQ(DateQ.q)
Protected I.DateQInfo : DateQInfo(@I, DateQ) : ProcedureReturn I\month
EndProcedure
Procedure DayQ(DateQ.q)
Protected I.DateQInfo : DateQInfo(@I, DateQ) : ProcedureReturn I\day
EndProcedure
Procedure HourQ(DateQ.q)
Protected I.DateQInfo : DateQInfo(@I, DateQ) : ProcedureReturn I\hour
EndProcedure
Procedure MinuteQ(DateQ.q)
Protected I.DateQInfo : DateQInfo(@I, DateQ) : ProcedureReturn I\min
EndProcedure
Procedure SecondQ(DateQ.q)
Protected I.DateQInfo : DateQInfo(@I, DateQ) : ProcedureReturn I\sec
EndProcedure
Procedure DayOfWeekQ(DateQ.q)
Protected I.DateQInfo : DateQInfo(@I, DateQ) : ProcedureReturn I\wday
EndProcedure
Procedure DayOfYearQ(DateQ.q)
Protected I.DateQInfo : DateQInfo(@I, DateQ) : ProcedureReturn I\yday
EndProcedure
Procedure DaysInMonth(DateQ.q)
Protected I.DateQInfo : DateQInfo(@I, DateQ)
If I\month = 2
ProcedureReturn 28 + IsLeapYear(I\year)
Else
ProcedureReturn (30 + I\month & 1) ! (I\month>>3)
EndIf
EndProcedure
Procedure.q AddDateQ(DateQ.q, Type, Value)
Protected I.DateQInfo, D.q
Select Type
Case #PB_Date_Year
DateQInfo(@I, DateQ) : I\year + Value
If I\month = 2 And I\yday = 60
If IsLeapYear(I\year) = 0 : I\day = 28 : EndIf
EndIf
DateQ = DateQ(I\year, I\month, I\day, I\hour, I\min, I\sec)
Case #PB_Date_Month
DateQInfo(@I, DateQ) : I\month + Value
While I\month < 1 : I\year - 1 : I\month + 12 : Wend
While I\month > 12 : I\year + 1 : I\month - 12 : Wend
DateQ = DateQ(I\year, I\month, I\day, I\hour, I\min, I\sec)
D = DateQ(I\year, I\month + 1, 0, I\hour, I\min, I\sec)
If DateQ > D : DateQ = D : EndIf
Case #PB_Date_Week : DateQ + Value * 604800
Case #PB_Date_Day : DateQ + Value * 86400
Case #PB_Date_Hour : DateQ + Value * 3600
Case #PB_Date_Minute : DateQ + Value * 60
Case #PB_Date_Second : DateQ + Value
EndSelect
ProcedureReturn DateQ
EndProcedure
Procedure FormatDateQ_Set(*addr.DateQ_CharArray, value, size = 2)
While size
size - 1 : *addr\c[size] = $30 | value % 10 : value / 10
Wend
EndProcedure
Procedure.s FormatDateQ(Mask$, DateQ.q)
Static Months.s = "JanFebMarAprMayJunJulAugSepOctNovDec"
Static Weekdays.s = "SunMonTueWedThuFriSat"
Protected *a.DateQ_CharArray = @Mask$
Protected.i p, f, r, w, l, I.DateQInfo, c1.c, c2.c, c.c = *a\c[0]
DateQInfo(@I, DateQ)
While c
p = w
If c = 37; %
c1 = *a\c[r + 1]
If c1
c1 | 32 : c2 = *a\c[r + 2] | 32
If c1 = c2
Select c1
Case 121; yy / yyyy
l = 2
If *a\c[r + 3] | 32 = c1
If *a\c[r + 4] | 32 = c1 : l = 4 : EndIf
EndIf
FormatDateQ_Set(@*a\c[w], I\year, l) : w + l : r + l + 1
Case 109; mm / mmm
If *a\c[r + 3] | 32 = c1
PokeS(@*a\c[w], Mid(Months, I\month * 3 - 2, 3)) : w + 3 : r + 4
Else
FormatDateQ_Set(@*a\c[w], I\month) : w + 2 : r + 3
EndIf
Case 100; dd / ddd
If *a\c[r + 3] | 32 = c1
PokeS(@*a\c[w], Mid(Weekdays, I\wday * 3 + 1, 3)) : w + 3 : r + 4
Else
FormatDateQ_Set(@*a\c[w], I\day) : w + 2 : r + 3
EndIf
Case 104; hh
FormatDateQ_Set(@*a\c[w], I\hour) : w + 2 : r + 3
Case 105; ii
FormatDateQ_Set(@*a\c[w], I\min) : w + 2 : r + 3
Case 115; ss
FormatDateQ_Set(@*a\c[w], I\sec) : w + 2 : r + 3
EndSelect
EndIf
EndIf
EndIf
If p = w
*a\c[w] = c : w + 1 : r + 1
EndIf
c = *a\c[r]
Wend
*a\c[w] = 0
ProcedureReturn Mask$
EndProcedure
Procedure WeekISO(DateQ.q)
Protected.i week1_prev, week1_this, week1_next, y = YearQ(DateQ)
Protected.i d = DateQ / 86400 + $afaa8
week1_prev = DateQ_LUT(y - 1)
week1_prev = (week1_prev >> 8) + 3 - (week1_prev >> 5 & 7 + 2) % 7
week1_this = DateQ_LUT(y)
week1_this = (week1_this >> 8) + 3 - (week1_this >> 5 & 7 + 2) % 7
week1_next = DateQ_LUT(y + 1)
week1_next = (week1_next >> 8) + 3 - (week1_next >> 5 & 7 + 2) % 7
If d < week1_this
ProcedureReturn (d - week1_prev) / 7 + 1
ElseIf d >= week1_next
ProcedureReturn 1
Else
ProcedureReturn (d - week1_this) / 7 + 1
EndIf
EndProcedure
Procedure DayFromYMW(Year, Month, Weekday, n = 1)
Protected I.DateQInfo, D.q = DateQ(Year, Month)
DateQInfo(@I, D)
D = DateQ(I\year, I\month, I\day + (Weekday - I\wday + 7) % 7 + n * 7 - 7)
DateQInfo(@I, D)
If I\month = Month
ProcedureReturn I\day
Else
ProcedureReturn -1
EndIf
EndProcedure