[bmx] Calendar

Started by Krischan, November 09, 2017, 18:30:12

Previous topic - Next topic

Krischan

This is a basic calendar output inspired by the Windows 10 calendar. It contains some useful functions like get the current UNIX timestamp from OS, create a formatted time output from a timestamp or calculate the current weekday and considers leap years (9/11 was on a tuesday, correct?). Could be optimized regarding the pre/after days and uses too many globals but can be a still good start to deal with a custom calender as I couldn't find any useful examples in the archives for Blitzmax. ;D

Use Arrow keys to cycle through months and years, limited to 1970-2100.

Code (Blitzmax) Select
SuperStrict

Framework brl.glmax2d
Import brl.retro

AppTitle = "Krischans Blitzmax Calendar"

' input year/month: current date
Local Scale:Float = 2
Local border:Int = 10
Local curdate:String = FTimestamp(GetTimeStamp(), "%Y%m")
Local y:Int = Int(Mid(curdate, 1, 4))
Local m:Int = Int(Mid(curdate, 5, 2))

' number of days and month names
Global days:Int[] = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
Global months:String[] = ["", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]

' output
Global out:String[10]
Global header:String[4]
Global pre:String
Global aft:String[4]
Global o:Int

' next month / old month
Global mm:Int
Global om:Int = Null

' next year / old year
Global yy:Int
Global oy:Int = Null

Graphics((160 * Scale) + (border * Scale * 2), (160 * Scale) + (border * Scale * 2), 0, 60)

' selected month/year must match input
mm = m
yy = y

CreateCalendar()

While Not (KeyHit(KEY_ESCAPE) Or AppTerminate())

Cls

' change month with LR arrows
mm:+(KeyHit(KEY_RIGHT) - KeyHit(KEY_LEFT))
If om <> mm Then

' wrap months and years
If mm < 1 Then mm = 12 ; yy:-1
If mm > 12 Then mm = 1 ; yy:+1

om = mm

CreateCalendar()

EndIf

' change year with UD arrows
yy:+(KeyHit(KEY_UP) - KeyHit(KEY_DOWN))
If oy <> yy Then

' limit years
If yy < 1970 Then yy = 1970
If yy > 2100 Then yy = 2100

oy = yy

CreateCalendar()

EndIf

' output calendar
SetScale(Scale, Scale)
SetColor(255, 255, 255)
SetBlend ALPHABLEND

' current month days
SetAlpha(0.8)
For Local i:Int = 0 To o

DrawText out[i], (border * Scale), (border * Scale) + (i * (15 * Scale))

Next

' days before and after to fill gaps
SetAlpha(0.25)
DrawText pre, (border * Scale), (border * Scale) + (4 * (15 * Scale))

For Local i:Int = 0 To 2

DrawText aft[i], (border * Scale), (border * Scale) + ((o + i) * (15 * Scale))

Next

Flip

Wend

End

' creates the calendar
Function CreateCalendar()

Local cnt1:Int = 0
Local cnt2:Int = 0
Local cnt3:Int = 0
Local mm2:Int

' reset output
out = New String[10]
pre = ""
aft = New String[4]
o = 0

' fix leap days
FixLeapYear(yy)

' calculate weekday of given month
Local weekday:Int = GetWeekDay(yy, mm, 1) Mod 7

' calender header
out[0] = months[mm] + " " + yy
out[1] = ""
out[2] = "Mo Tu We Th Fr Sa Su"
out[3] = "===================="

' calendar whitespaces
If weekday > 0 Then

' fill gaps if week doesn't start at monday
For Local d:Int = 1 To weekday

out[4]:+"   "

Next

' previous month
mm2 = mm - 1
If mm2 < 1 Then mm2 = 12

' add previous month days (pre)
For Local d:Int = days[mm2] - weekday + 1 To days[mm2]

pre:+FormatDay(d)

Next

o = 4

Else

o = 3

EndIf

' calendar days
For Local d:Int = 1 To days[mm]

' count day on current row
cnt1:+1

' next row if sunday
If (d + weekday - 1) Mod 7 = 0 Then o:+1 ; cnt1 = 0

' add row to output
out[o]:+FormatDay(d)

Next

' month stops and it is not sunday?
If cnt1 <> 6 Then

' next month
mm2 = mm + 1
If mm2 > 12 Then mm2 = 1

' fill gaps
For Local i:Int = 0 To cnt1

aft[0]:+"   "

Next

' add next month days (aft)
For Local d:Int = 1 To 6 - cnt1

aft[0]:+FormatDay(d)
cnt2:+1

Next

EndIf

' fill additional aft rows
If o < 9 Then

' next month
mm2 = mm + 1
If mm2 > 12 Then mm2 = 1

For Local i:Int = 0 To 8 - o

' add next month days (aft)
For Local d:Int = cnt2 + cnt3 + 1 To cnt2 + cnt3 + 7

aft[1 + i]:+FormatDay(d)
cnt3:+1

Next

' prepare next row (if short month like Feb. 1993)
cnt3 = cnt2 + 7

Next

EndIf

End Function

' output formatted day
Function FormatDay:String(d:Int)

Local day:String = d + " "
If d < 10 Then day = " " + d + " "

Return day

End Function

' get day of week for a given date (0-6, 0=Monday)
Function GetWeekDay:Int(y:Int, m:Int, d:Int)

Local t:Int[] = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]

If m < 3 Then y:-1

Return ((y + y / 4 - y / 100 + y / 400 + t[m - 1] + d) - 1) Mod 7

End Function

' fix leap years in months array
Function FixLeapYear:Int(year:Int)

If (((year Mod 4 = 0) And (year Mod 100 <> 0)) Or (year Mod 400 = 0)) Then

days[2] = 29

Else

days[2] = 28

EndIf

End Function

' returns formatted time string
Function FTimestamp:String(timestamp:Int, format:String = "%Y-%m-%d %H:%M:%S")

Local time:Int Ptr, buff:Byte[256]

time = VarPtr(timestamp)
strftime_(buff, 256, format, localtime_(time))

Return String.FromCString(buff)

End Function

' get current UNIX timestamp from OS
Function GetTimeStamp:Int()

Local time:Int[256]
time_(time)
return time[0]

End Function

' returns the exact! unix timestamp for given YEARS,MONTHS,DAYS,HOURS,MINUTES,SECONDS (not used in this example)
Function GetUnixTimestamp:Int(yy:Int, mm:Int, dd:Int, hh:Int, ii:Int, ss:Int)

Local days:Int[] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
Local years:Int = yy - 1970
Local leap:Int = ((yy - 1) - 1968) / 4 - ((yy - 1) - 1900) / 100 + ((yy - 1) - 1600) / 400

Local unixtime:Int = ss + 60 * ii + 60 * 60 * hh + (days[mm - 1] + dd - 1) * 60 * 60 * 24 + (years * 365 + leap) * 60 * 60 * 24

  If ((mm > 2) And (yy Mod 4 = 0 And (yy Mod 100 <> 0 Or yy Mod 400 = 0))) Then

' leap day
unixtime:+(60 * 60 * 24)

End If

  Return unixtime

End Function
Kind regards
Krischan

Windows 10 Pro | i7 9700K@ 3.6GHz | RTX 2080 8GB]
Metaverse | Blitzbasic Archive | My Github projects

degac

#1
Hi

I've collected & rearranged some 'temporal' functions about date & time! (I need for some apps I'm using at work!)
And - after many google's jump - I think I've found the 'right' functions about time conversion (tested the results with many 'time converter' online, so I'm quite sure they works well.

ps: there's a function 'Pad() that's is not defined in this 'library'... just use a simple string in case

pps: for MaxGUI I wrote (many years ago, with the help of Sebholl!) a 'calendar' gadget... that' mimics the OS one (! personal note I should check the temporal functions in it!) :D

Rem
Library Time Function

04.11.2017
Time Difference ! difference between two dates: 01.01.2017 and 01.01.2017 should be ZERO??

fixed: support for 2 date format (input) DDMMYYYY ir DD/MM/YEAR

*** main bug: Conversion to/from Julian was wrong!!
*** changed the Julian2 Gregorian function too...



---
ChangeDate()
GetDayOfTheWeek:INT( date) ... 0 Sunday...
IsLeap( date ) ... true or false
TimeDifference (type, date1, date2) where type="D" for days, "M" for month, "Y" for years
Conv_Date2Julian(date)
Conv_Julian2Date(julian_value,date value)

End Rem

Global _localized_month:String[]=["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"]
Function ChangeDate:String(mese:Int=1)
Return _localized_month[mese-1]
End Function


Function GetDayOfTheWeek:Int(_data$="")
Rem
return an INT 0 to 6
0 = sunday... 1 =monday 2=tuesday...
End Rem

Local year:Int,month:Int,day:Int
If _data="" Return -1
If _data.contains("/")=False
_data=_data[..2]+"/"+_data[2..4]+"/"+_data[4..]
End If
year=Int(_data[6..])
month=Int(_data[3..5])
day=Int(_data[..2])

Local A:Int,Y:Int,M:Int
Local D:Int
a = (14 - month) / 12
y = year - a
m = month + 12*a - 2

Return ((day + y + y/4 - y/100 + y/400 + (31*m)/12))Mod 7

End Function

Function IsLeap:Int(SomeDate:String)   ' Pass either just the year, or a "DD MMM YYYY" BlitzMax data string
Local Jaar:Int=Int(Right$(SomeDate,4))
If ((Jaar Mod 4) = 0 And (Jaar Mod 100) <> 0) Or ((Jaar Mod 4) = 0 And (Jaar Mod 400) = 0) Then
' Leap Year -- Any year divisible by 4, except the centuries unless they are multiples of 400
Return True
Else
' Not a Leap Year
Return False
End If
End Function

'------------------------------- temporal conversion ------------------------------


Function Conv_Date2Julian:Float(_data$="")
If _data="" Return -1
If _data.contains("/")=False
_data=_data[..2]+"/"+_data[2..4]+"/"+_data[4..]
End If

Local year:Int,month:Int,day:Int

year=Int(_data[6..])
month=Int(_data[3..5])
day=Int(_data[..2])
' Print "Date : "+_data+" : "+day+" "+month+" "+year

Local a:Int,b:Int,c:Int,d:Int
Local e:Int,f:Int,jd:Float

a=year/100
b=a/4
c=2-a+b
e=365.25*(year+4716)
f=30.6001*(month+1)
jd=Float(c+day+e+f-1524.5)
Return JD

End Function

Function old_Conv_Date2Julian:Int(_data$="")
'accept data format 15022017 or 15/02/2017
If _data="" Return -1
If _data.contains("/")=False
_data=_data[..2]+"/"+_data[2..4]+"/"+_data[4..]
End If

Local year:Int,month:Int,day:Int

year=Int(_data[6..])
month=Int(_data[3..5])
day=Int(_data[..2])

'Print "Date to convert: "+day+" "+month+" "+year

Local JulianDate:Int
' conversion taken from a website (lost link)
JulianDate = 367 * Year - ((7 * (Year + 5001 + ((Month - 9) / 7))) / 4) + ((275 * Month) / 9) + Day + 1729777

Return JulianDate
End Function

Function Conv_Julian2Date:Int(Jd:Float,date$ Var)

Local L:Float,N:Int,I:Int,K:Int,YEAR:Int,MONTH:Int,DAY:Int,J:Int
'Print "JD :"+(jd-Int(jd))
L= JD+68569
    N= 4*L/146097

    L= Int(L-(146097*N+3)/4)
    I= 4000*(L+1)/1461001
    L= L-1461*I/4+31
    J= 80*L/2447
    K= L-2447*J/80
    L= J/11
    J= J+2-12*L
    I= 100*(N-49)+I+L
   
YEAR= I
    MONTH= J
    DAY= K+(jd-Int(jd))

date=pad(day,"00",2)+"/"+pad(month,"00",2)+"/"+pad(year,"0000",4)

End Function


Function old_Conv_Julian2Date(pJulian:Int, date$ Var)
Local l:Int
Local k:Int
Local n:Int
Local i:Int
Local j:Int
Local day:Int,month:Int,year:Int

' conversion taken from a website (lost link)
j = pJulian + 1402
k = ((j - 1) / 1461)
l = j - 1461 * k
n = ((l - 1) / 365) - (l / 1461)
i = l - 365 * n + 30
j = ((80 * i) / 2447)
Day = i - ((2447 * j) / 80)
i = (j / 11)
Month = j + 2 - 12 * i
Year = 4 * k + n + i - 4716

date=pad(day,"00",2)+"/"+pad(month,"00",2)+"/"+pad(year,"0000",4)
End Function

Function TimeDifference:Int(pInterval$, date1$="",date2$="")
'date format 15/02/2017
Local i:Int
Local j:Int

If date1="" Or date2="" Return -1

pInterval=Upper(pinterval)

Local year1:Int,year2:Int
Local month1:Int,month2:Int
Local day1:Int,day2:Int

If date1.contains("/")=False
date1=date1[..2]+"/"+date1[2..4]+"/"+date1[4..]
End If

If date2.contains("/")=False
date2=date2[..2]+"/"+date2[2..4]+"/"+date2[4..]
End If



year1=Int(date1[6..])
month1=Int(date1[3..5])
day1=Int(date1[..2])

year2=Int(date2[6..])
month2=Int(date2[3..5])
day2=Int(date2[..2])



Select pInterval$
Case "Y" 'Year
i = Year1 - Year2
Case "M" 'Month
i = (Year2 - Year1) * 12 + (Month2 - Month1)
Case "D" 'Day
'Converts in julian to substract days
i = Conv_Date2Julian(Date2) - Conv_Date2Julian(Date1)'+1
End Select

Return i
End Function
If there's a problem, there's at least one solution.
www.blitzmax.org

Derron


Quote from: degac[/size]   04.11.2017    Time Difference ! difference between two dates: 01.01.2017 and 01.01.2017 should be ZERO??[/size]

Of course it should. Substitute equal values with X ... so you ask: "difference between two dates: x and x should be ZERO??" - yes, as x-x = 0.This works also if you think about what the date means. Yes, there is no exact time given (hours, minutes...) but this is not needed as both dates are using your personal definition of either time or assumption. Means, if you only give a date, then the hours are not of importance. If they would be important, you would have mentioned them. Mathematically this means, that a given date without hour:minute:second:... should always assume the same "time of that day" (eg 0:00:00:...). And then calculation is possible without much hassle.


31.12.2016 23:50 to 01.01.2017 would then result in 10 minutes difference.byeRon

degac

LOL!
Let me explain... is not a dumb question as it could be :)
As I'm said, I'm using these functions in some applications at work.
Some of these are needed to calculate stock's age... so this is a remark for me about how to consider (or not) in the calculation.... the function returns (mathematically correct) the 'zero' value (of course!) (see the returned value, there's still the '+1 commented out!)
If there's a problem, there's at least one solution.
www.blitzmax.org

Derron

I fully understand what you are saying.


In may game I have similar issues: GetDaysGone() and GetOnDay().  The latter one adds the +1 ;-)
In your application (stocks...) you will also never say "age = 1 day" if it is "1.1.2017 12:00" and the stock was created on "1.1.2017". In that case "GetAgeInDays()" should always return 0 - while "GetAgeInHours()" could return 12.


And if you want to know something like "stock's 3rd day on the market..." then you will add +1 in that function (which is my "GetOnDay()" doing).




bye
Ron

Krischan

#5
Here is a version which displays the current day, too. I've cross-checked my calendar with various sources in the internet and it should work at least until 1600 AD, or even farther back in time. Before 1600 the calculations vary but I think we don't really need this (the most important function is to determine the weekday of a given date and to identify leap years or the calender will display a wrong sheet, I've used a slightly modified Sakamoto's algorithm as it was easy to implement and seemed to work best) ::)

Code (Blitzmax) Select
SuperStrict

Framework brl.glmax2d
Import brl.retro

AppTitle = "Krischans Blitzmax Calendar"

' input year/month: current date
Local Scale:Float = 2
Local border:Int = 10
Local curdate:String = FTimestamp(GetTimeStamp(), "%Y%m%d")
Global curyear:Int = Int(Mid(curdate, 1, 4))
Global curmonth:Int = Int(Mid(curdate, 5, 2))
Global curday:Int = Int(Mid(curdate, 7, 2))

' number of days and month names
Global days:Int[] = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
Global months:String[] = ["", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]

' output
Global out:String[10]
Global header:String[4]
Global pre:String
Global aft:String[4]
Global o:Int

' next month / old month
Global mm:Int
Global om:Int = Null

' next year / old year
Global yy:Int
Global oy:Int = Null

Global dd:Int
Global highx:Int
Global highy:Int

Graphics((160 * Scale) + (border * Scale * 2), (160 * Scale) + (border * Scale * 2), 0, 60)

' selected month/year must match input
dd = curday
mm = curmonth
yy = curyear

CreateCalendar()

While Not (KeyHit(KEY_ESCAPE) Or AppTerminate())

Cls

' change month with LR arrows
mm:+(KeyHit(KEY_RIGHT) - KeyHit(KEY_LEFT))
If om <> mm Then

' wrap months and years
If mm < 1 Then mm = 12 ; yy:-1
If mm > 12 Then mm = 1 ; yy:+1

om = mm

CreateCalendar()

EndIf

' change year with UD arrows
yy:+(KeyHit(KEY_UP) - KeyHit(KEY_DOWN))
If oy <> yy Then

' limit years
If yy < 1 Then yy = 1
If yy > 2100 Then yy = 2100

oy = yy

CreateCalendar()

EndIf

' output calendar
SetScale(Scale, Scale)
SetColor(255, 255, 255)
SetBlend ALPHABLEND

' current month days
SetAlpha(0.8)
For Local i:Int = 0 To o

DrawText out[i], (border * Scale), (border * Scale) + (i * (15 * Scale))

Next

' days before and after to fill gaps
SetAlpha(0.25)
DrawText pre, (border * Scale), (border * Scale) + (4 * (15 * Scale))

For Local i:Int = 0 To 2

DrawText aft[i], (border * Scale), (border * Scale) + ((o + i) * (15 * Scale))

Next

If highx And highy Then

SetColor(64, 64, 64)
SetAlpha(1.0)
Local x:Int = (border * Scale) + (highx * TextWidth("   ") * Scale)
Local y:Int = (border * Scale) + (highy * 15 * Scale)
Local w:Int = TextWidth(" ") * Scale
Local h:Int = TextHeight(" ") / 2 * Scale

DrawRect(x * w, y * h, w, h)
SetColor(0, 255, 0)
DrawText curday, x, y

EndIf

Flip

Wend

End

' creates the calendar
Function CreateCalendar()

Local cnt1:Int = 0
Local cnt2:Int = 0
Local cnt3:Int = 0
Local mm2:Int

' reset output
out = New String[10]
pre = ""
aft = New String[4]
o = 0
highx = 0
highy = 0

' fix leap days
FixLeapYear(yy)

' calculate weekday of given month
Local weekday:Int = GetWeekDay(yy, mm, 1) Mod 7

' calender header
out[0] = months[mm] + " " + yy
out[1] = ""
out[2] = "Mo Tu We Th Fr Sa Su"
out[3] = "===================="

' calendar whitespaces
If weekday > 0 Then

' fill gaps if week doesn't start at monday
For Local d:Int = 1 To weekday

out[4]:+"   "

Next

' previous month
mm2 = mm - 1
If mm2 < 1 Then mm2 = 12

' add previous month days (pre)
For Local d:Int = days[mm2] - weekday + 1 To days[mm2]

pre:+FormatDay(d)

Next

o = 4

Else

o = 3

EndIf

' calendar days
For Local d:Int = 1 To days[mm]

' count day on current row
cnt1:+1

' next row if sunday
If (d + weekday - 1) Mod 7 = 0 Then o:+1 ; cnt1 = 0

If d = dd And mm = curmonth And yy = curyear Then

highx = cnt1
highy = o

EndIf

' add row to output
out[o]:+FormatDay(d)

Next

' month stops and it is not sunday?
If cnt1 <> 6 Then

' next month
mm2 = mm + 1
If mm2 > 12 Then mm2 = 1

' fill gaps
For Local i:Int = 0 To cnt1

aft[0]:+"   "

Next

' add next month days (aft)
For Local d:Int = 1 To 6 - cnt1

aft[0]:+FormatDay(d)
cnt2:+1

Next

EndIf

' fill additional aft rows
If o < 9 Then

' next month
mm2 = mm + 1
If mm2 > 12 Then mm2 = 1

For Local i:Int = 0 To 8 - o

' add next month days (aft)
For Local d:Int = cnt2 + cnt3 + 1 To cnt2 + cnt3 + 7

aft[1 + i]:+FormatDay(d)
cnt3:+1

Next

' prepare next row (if short month like Feb. 1993)
cnt3 = cnt2 + 7

Next

EndIf

End Function

' output formatted day
Function FormatDay:String(d:Int)

Local day:String = d + " "
If d < 10 Then day = " " + d + " "

Return day

End Function

' get day of week for a given date (0-6, 0=Monday)
Function GetWeekDay:Int(y:Int, m:Int, d:Int)

Local t:Int[] = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]

If m < 3 Then y:-1

Return ((y + y / 4 - y / 100 + y / 400 + t[m - 1] + d) - 1) Mod 7

End Function

' fix leap years in months array
Function FixLeapYear:Int(year:Int)

'If (((year Mod 4 = 0) And (year Mod 100 <> 0)) Or (year Mod 400 = 0)) Then

If ((year Mod 4) = 0 And (year Mod 100) <> 0) Or ((year Mod 4) = 0 And (year Mod 400) = 0) Then


days[2] = 29

Else

days[2] = 28

EndIf

End Function

' returns formatted time string
Function FTimestamp:String(timestamp:Int, format:String = "%Y-%m-%d %H:%M:%S")

Local time:Int Ptr, buff:Byte[256]

time = VarPtr(timestamp)
strftime_(buff, 256, format, localtime_(time))

Return String.FromCString(buff)

End Function

' get current UNIX timestamp from OS
Function GetTimeStamp:Int()

Local time:Int[256]
time_(time)
return time[0]

End Function

' returns the exact! unix timestamp for given YEARS,MONTHS,DAYS,HOURS,MINUTES,SECONDS (not used in this example)
Function GetUnixTimestamp:Int(yy:Int, mm:Int, dd:Int, hh:Int, ii:Int, ss:Int)

Local days:Int[] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
Local years:Int = yy - 1970
Local leap:Int = ((yy - 1) - 1968) / 4 - ((yy - 1) - 1900) / 100 + ((yy - 1) - 1600) / 400

Local unixtime:Int = ss + 60 * ii + 60 * 60 * hh + (days[mm - 1] + dd - 1) * 60 * 60 * 24 + (years * 365 + leap) * 60 * 60 * 24

  If ((mm > 2) And (yy Mod 4 = 0 And (yy Mod 100 <> 0 Or yy Mod 400 = 0))) Then

    ' leap day
unixtime:+(60 * 60 * 24)

End If

  Return unixtime

End Function
Kind regards
Krischan

Windows 10 Pro | i7 9700K@ 3.6GHz | RTX 2080 8GB]
Metaverse | Blitzbasic Archive | My Github projects