Monday, July 27, 2015

Microsoft OUTLOOK Recurring Event ACCESS VBA code


Microsoft Outlook provides a nice calendar program that allows people to track events. The Outlook, like all other components in the Microsoft Office suite, also provides program interfaces (API) for programmer to extend their use of the program.

In our office, we try to pull these info into our databases. For normal events, the code is straightforward since it is stored in a day by day basis. For recurring events, however, in practical, it can't be stored in a day by day basis since some recurring events don't have an end day. This, therefore, presents a challenge when try to pull in calendar events on a day by day basis. Basically, the program need to go through each recurring event and, based on the pattern of each recurring event, generates the day and time of each occurrences and find out if any of the occurrences fall inside the specific time period of interest.

Below are codes that I devised and I hope it is of value to someone - please provide adequate credit statement if you do adopt and find the code useful.

Few words about the program:
  • xCDO is an object that provides access to database.
  • PrmtrTyp, Prmtr are just for me to extend code in the future. 1 and "" are normally used.
  • BgnDt, and EndDt specify the period of interest.
  • xAppntmnt is the Appointment object of Outlook.
  • xHC is simply my version of the VBA collection object. It allows to set entries via StItm() function and retrieve value via RdItm() function.
  • The handling of yearly recurring events is to be amended.

Public Function DoRecrrngs(aCDO, BgnDt, EndDt, aAppntmnts, Ppl, PrmtrTyp, Prmtr)
    Dim status, oAppntmnt, oPttrn
    Dim oHC As New HCllctn
   
    For Each oAppntmnt In aAppntmnts
        Set oPttrn = oAppntmnt.GetRecurrencePattern
        If oPttrn.PatternEndDate >= BgnDt Then
            status = oHC.StItm("BgnDt", BgnDt)
            status = oHC.StItm("EndDt", EndDt)
            status = oHC.StItm("IsAllDy", oAppntmnt.AllDayEvent)
            status = oHC.StItm("EvntTtl", oAppntmnt.Subject)
            status = oHC.StItm("EvntDtl", oAppntmnt.Location)
            status = oHC.StItm("Ppl", Ppl)
            status = DoRecrrng(aCDO, oHC, oPttrn, PrmtrTyp, Prmtr)
        End If
    Next
End Function
Public Function DoRecrrng(aCDO, aHC, aPttrn, PrmtrTyp, Prmtr)
    Dim status

    Select Case aPttrn.RecurrenceType
        Case 0  ' Daily
            status = DoRecrrng0_(aCDO, aHC, aPttrn, PrmtrTyp, Prmtr)
        Case 1  ' Weekly
            status = DoRecrrng1_(aCDO, aHC, aPttrn, PrmtrTyp, Prmtr)
'           status = -1111
        Case 2  ' Monthly (Day of Month)
            status = DoRecrrng2_(aCDO, aHC, aPttrn, PrmtrTyp, Prmtr)
'           status = -1121
        Case 3  ' MonthNth (weekday of Nth week)
            status = DoRecrrng3_(aCDO, aHC, aPttrn, PrmtrTyp, Prmtr)
'           status = -1131
        Case 4  ' ??
            status = -1141
        Case 5  ' Yearly (Day of Month)
            status = -1151
        Case 6  ' YearNth (Day of Nth week of Month)
            status = -1161
    End Select
    DoRecrrng = status
End Function

' Daily - assuming aPttrn.PatternEndDate >= BgnDt
Public Function DoRecrrng0_(aCDO, aHC, aPttrn, PrmtrTyp, Prmtr)
    Dim status, tmpDt, tmpNo, NDay
    Dim BgnDt, EndDt, IsAllDy, StrtTm, EndTm, Ppl, EvntTtl, EvntDtl

    ' Retrieve useful info to local
    BgnDt = aHC.RdItm("BgnDt"): EndDt = aHC.RdItm("EndDt")
    IsAllDy = aHC.RdItm("IsAllDy"): Ppl = aHC.RdItm("Ppl")
    StrtTm = aPttrn.StartTime: EndTm = aPttrn.EndTime
    EvntTtl = strRplc("""", "", aHC.RdItm("EvntTtl")): EvntDtl = strRplc("""", "", aHC.RdItm("EvntDtl"))
    NDay = aPttrn.Interval ' Every N day
    tmpDt = aPttrn.PatternStartDate
    If tmpDt < BgnDt Then
        tmpNo = DateDiff("d", tmpDt, BgnDt)
        tmpNo = tmpNo + NDay - (tmpNo Mod NDay)
        tmpDt = DateAdd("d", tmpNo, tmpDt)
    End If
    DoRecrrng0_ = 0
    Do
        If tmpDt <= EndDt Then
            DoRecrrng0_ = DoRecrrng0_ + 1
            ' save the event to db
            status = Add2EvntTmp(aCDO, IsAllDy, tmpDt + StrtTm, tmpDt + EndTm, _
                Ppl, EvntTtl, EvntDtl)
            tmpDt = DateAdd("d", NDay, tmpDt)
        End If
    Loop Until tmpDt > EndDt
End Function

' Weekly - assuming aPttrn.PatternEndDate > BgnDt
Public Function DoRecrrng1_(aCDO, aHC, aPttrn, PrmtrTyp, Prmtr)
    Dim status, tmpNo, tmpDt, NWk, WkDyBt, WkDyMsk
    Dim BgnDt, EndDt, IsAllDy, StrtTm, EndTm, Ppl, EvntTtl, EvntDtl

    ' Retrieve useful info to local
    BgnDt = aHC.RdItm("BgnDt"): EndDt = aHC.RdItm("EndDt")
    IsAllDy = aHC.RdItm("IsAllDy"): Ppl = aHC.RdItm("Ppl")
    StrtTm = aPttrn.StartTime: EndTm = aPttrn.EndTime
    EvntTtl = strRplc("""", "", aHC.RdItm("EvntTtl")): EvntDtl = strRplc("""", "", aHC.RdItm("EvntDtl"))
    NWk = aPttrn.Interval ' Every N week
    If NWk = 0 Then NWk = 1 ' Adjust Outlook daily weekday assigned value
    ' Prepare for begin date
    tmpDt = aPttrn.PatternStartDate
    If tmpDt < BgnDt Then
        tmpNo = DateDiff("ww", tmpDt, BgnDt)
        tmpNo = tmpNo + NWk - (tmpNo Mod NWk)
        tmpDt = DateAdd("ww", tmpNo, tmpDt)
        tmpDt = DateAdd("d", 1 - DatePart("w", tmpDt), tmpDt)   ' Adjusted to Sunday
    End If
    ' Prepare weekday detection
    WkDyBt = 2 ^ (DatePart("w", tmpDt) - 1)
    WkDyMsk = aPttrn.DayOfWeekMask
    DoRecrrng1_ = 0
    Do Until tmpDt > EndDt
        If WkDyBt And WkDyMsk Then
            ' save the event to db
            status = Add2EvntTmp(aCDO, IsAllDy, tmpDt + StrtTm, tmpDt + EndTm, _
                Ppl, EvntTtl, EvntDtl)
        End If
        If WkDyBt = 64 Then
            tmpDt = DateAdd("ww", NWk, tmpDt)
            tmpDt = DateAdd("d", 1 - DatePart("w", tmpDt), tmpDt)   ' Adjusted to Sunday
            WkDyBt = 1
        Else
            tmpDt = DateAdd("d", 1, tmpDt)
            WkDyBt = WkDyBt * 2
        End If
    Loop
End Function

' Monthly (day of month) - assuming aPttrn.PatternEndDate > BgnDt
Public Function DoRecrrng2_(aCDO, aHC, aPttrn, PrmtrTyp, Prmtr)
    Dim status, tmpNo, tmpDt, NMnth, NthDy
    Dim BgnDt, EndDt, IsAllDy, StrtTm, EndTm, Ppl, EvntTtl, EvntDtl

    ' Retrieve useful info to local
    BgnDt = aHC.RdItm("BgnDt"): EndDt = aHC.RdItm("EndDt")
    IsAllDy = aHC.RdItm("IsAllDy"): Ppl = aHC.RdItm("Ppl")
    StrtTm = aPttrn.StartTime: EndTm = aPttrn.EndTime
    EvntTtl = strRplc("""", "", aHC.RdItm("EvntTtl")): EvntDtl = strRplc("""", "", aHC.RdItm("EvntDtl"))
    NMnth = aPttrn.Interval ' Every Nth month
    NthDy = aPttrn.DayOfMonth ' Nth day
    tmpDt = aPttrn.PatternStartDate
    If tmpDt < BgnDt Then
        tmpNo = DateDiff("m", tmpDt, BgnDt)
        tmpNo = tmpNo + NMnth - (tmpNo Mod NMnth)
        tmpDt = DateAdd("m", tmpNo, tmpDt)
    End If
    ' Adjust the date
    tmpDt = DateSerial(Year(tmpDt), Month(tmpDt), NthDy)
    ' Loop until pass EndDt
    DoRecrrng2_ = 0
    Do Until tmpDt > EndDt
        If tmpDt >= BgnDt And tmpDt <= EndDt Then ' Double sure
            DoRecrrng2_ = DoRecrrng2_ + 1
            ' save the event to db
            status = Add2EvntTmp(aCDO, IsAllDy, tmpDt + StrtTm, tmpDt + EndTm, _
                Ppl, EvntTtl, EvntDtl)
        End If
        tmpDt = DateAdd("m", NMnth, tmpDt)
        tmpDt = DateSerial(Year(tmpDt), Month(tmpDt), NthDy)
    Loop
End Function

' MonthNth (Nth weekday) - assuming aPttrn.PatternEndDate > BgnDt
Public Function DoRecrrng3_(aCDO, aHC, aPttrn, PrmtrTyp, Prmtr)
    Dim status, tmpNo, tmpDt, NMnth, NthWkDy, BtNoArry(), tmpFlg
    Dim BgnDt, EndDt, IsAllDy, StrtTm, EndTm, Ppl, EvntTtl, EvntDtl

    ' Retrieve useful info to local
    BgnDt = aHC.RdItm("BgnDt"): EndDt = aHC.RdItm("EndDt")
    IsAllDy = aHC.RdItm("IsAllDy"): Ppl = aHC.RdItm("Ppl")
    StrtTm = aPttrn.StartTime: EndTm = aPttrn.EndTime
    EvntTtl = strRplc("""", "", aHC.RdItm("EvntTtl")): EvntDtl = strRplc("""", "", aHC.RdItm("EvntDtl"))
    NMnth = aPttrn.Interval ' Every Nth month
    NthWkDy = aPttrn.Instance
    tmpDt = aPttrn.PatternStartDate
    ' To the month just before BgnDt
    If tmpDt < BgnDt Then
        tmpNo = Year(BgnDt) * 12 + Month(BgnDt) - Year(tmpDt) * 12 - Month(tmpDt)
        tmpNo = tmpNo + NMnth - (tmpNo Mod NMnth)
        tmpDt = DateAdd("m", tmpNo, tmpDt)
    End If
    ' Get WkDy - MonthNth can't have multiple week day!
    tmpFlg = Lng2BtNoArry(aPttrn.DayOfWeekMask, BtNoArry, 1, "")
'   If tmpNo > 1 Then   ' weekday or weekend day
'       tmpFlg = tmpNo
'       DoRecrrng3_ = -1111 ' not handled for now
'       DoRecrrng3_ = DoRecrrng3x_(aCDO, aHC, aPttrn, PrmtrTyp, Prmtr)
'        Exit Function
'   End If
    tmpNo = BtNoArry(1) ' Only one can be selected
    ' Adjust the date
    If tmpFlg = 2 Or tmpFlg = 5 Then
        tmpDt = NthWrkWkndDy2Dt(NthWkDy, tmpFlg, Month(tmpDt), Year(tmpDt), 1, "")
    Else
        tmpDt = NthWkDy2Dt(NthWkDy, tmpNo, Month(tmpDt), Year(tmpDt), 1, "")
    End If
    ' Loop until pass EndDt
    DoRecrrng3_ = 0
    Do Until tmpDt > EndDt
        If tmpDt >= BgnDt And tmpDt <= EndDt Then ' Double sure
            DoRecrrng3_ = DoRecrrng3_ + 1
            ' save the event to db
            status = Add2EvntTmp(aCDO, IsAllDy, tmpDt + StrtTm, tmpDt + EndTm, _
                Ppl, EvntTtl, EvntDtl)
        End If
        tmpDt = DateAdd("m", NMnth, tmpDt)
        If tmpFlg = 2 Or tmpFlg = 5 Then ' Adjust the date
            tmpDt = NthWrkWkndDy2Dt(NthWkDy, tmpFlg, Month(tmpDt), Year(tmpDt), 1, "")
        Else
            tmpDt = NthWkDy2Dt(NthWkDy, tmpNo, Month(tmpDt), Year(tmpDt), 1, "")
        End If
    Loop
End Function

' Give the nth workday or weekend day and return the date
'   Typ: 2(weekend day) 5(work day) other(error)
Public Function NthWrkWkndDy2Dt(aNth, aTyp, aMnth, aYr, PrmtrTyp, Prmtr)
    Dim tmpDt

    NthWrkWkndDy2Dt = Null
    If aNth < 5 Then
        tmpDt = DateSerial(aYr, aMnth, 1)
        Cnt = 0
        Do Until Cnt = aNth
            WkDy = DatePart("w", tmpDt)
            If aTyp = 2 And (WkDy = 1 Or WkDy = 7) Then
                Cnt = Cnt + 1
            ElseIf aTyp = 5 And WkDy > 1 And WkDy < 7 Then
                Cnt = Cnt + 1
            End If
            NthWrkWkndDy2Dt = tmpDt
            tmpDt = DateAdd("d", 1, tmpDt)
        Loop
    ElseIf aNth = 5 Then
        tmpDt = DateSerial(aYr, aMnth, LstDyAMnth(aYr, aMnth, 1, ""))
        Cnt = 0
        Do Until Cnt > 0
            WkDy = DatePart("w", tmpDt)
            If aTyp = 2 And (WkDy = 1 Or WkDy = 7) Then
                Cnt = 1
            ElseIf aTyp = 5 And WkDy > 1 And WkDy < 7 Then
                Cnt = 1
            End If
            NthWrkWkndDy2Dt = tmpDt
            tmpDt = DateAdd("d", -1, tmpDt)
        Loop
    End If
End Function

' Given nth weekday and return the date
' nth(5=last, <1 error="">5=error)
Public Function NthWkDy2Dt(aNth, aDyWk, aMnth, aYr, PrmtrTyp, Prmtr)
    Dim Dt

    If aNth > 5 Then
        NthWkDy2Dt = Null
        Exit Function
    End If
    ' 1st day of that month
    Dt = DateSerial(aYr, aMnth, 1)
    If aDyWk < Weekday(Dt) Then
        Dt = DateAdd("d", aNth * 7 + aDyWk - Weekday(Dt), Dt)
    Else
        Dt = DateAdd("d", (aNth - 1) * 7 + aDyWk - Weekday(Dt), Dt)
    End If
    If aMnth < Month(Dt) Then
        Dt = DateAdd("d", -7, Dt)
    End If
    NthWkDy2Dt = Dt
End Function

' BtNoArry(0)=number of bits numbers
' BtNoArry(n)=bit number + 1
' PrmtrTyp 0(reserved),
'   1(array of bit numbers where the bit is 1)
'   2(array of bits)
Public Function Lng2BtNoArry(Lng, BtNoArry, PrmtrTyp, Prmtr)
    Dim Sz, Ndx, NoItm
    Sz = Int(Log(Lng) / Log(2)) + 1
    ReDim BtNoArry(Sz)
    NoItm = 0
    If PrmtrTyp = 1 Then
        For Ndx = 1 To Sz
            If (Lng Mod 2) = 1 Then
                NoItm = NoItm + 1
                BtNoArry(NoItm) = Ndx
                Lng = Lng - 1
            End If
            Lng = Lng / 2
        Next
    Else
        For Ndx = 1 To Sz
            BtNoArry(Ndx) = Lng Mod 2
            Lng = Int(Lng / 2)
        Next
        NoItm = Sz
    End If
    BtNoArry(0) = NoItm
    Lng2BtNoArry = NoItm
End Function

' Save outlook appointment to event table
Public Function Appntmnt2EvntTmp(aCDO, Appntmnt, Ppl, PrmtrTyp, Prmtr)
    Appntmnt2EvntTmp = Add2EvntTmp(aCDO, _
        Appntmnt.AllDayEvent, _
        Appntmnt.Start, _
        Appntmnt.End, _
        Ppl, _
        strRplc("""", "", Appntmnt.Subject), _
        strRplc("""", "", Appntmnt.Location) _
    )
End Function
' Save parameters to event table
Public Function Add2EvntTmp(aCDO, IsAllDy, TmBgn, TmEnd, Ppl, EvntTtl, EvntDtl)
    sSql = "INSERT INTO EvntTmp(AllDyEvnt, TmBgn,TmEnd,OwnrNm,EvntTtl,EvntDtl) VALUES" _
        & "(" & IsAllDy & ",""" & TmBgn & """,""" & TmEnd & """,""" & Ppl _
        & """,""" & EvntTtl & """,""" & EvntDtl & """);"
    Add2EvntTmp = aCDO.Exct(sSql, 1, "")
End Function

' return the last day of a month
' PrmtrTyp: 0(reserved) 1(implemented)
Public Function LstDyAMnth(Yr, Mnth, PrmtrTyp, Prmtr)
    Select Case Mnth
        Case 1, 3, 5, 7, 8, 10, 12
            LstDyAMnth = 31
        Case 4, 6, 9, 11
            LstDyAMnth = 30
        Case 2
            LstDyAMnth = DatePart("d", DateAdd("m", 1, DateSerial(Yr, 1, 31)))
        Case Else
            LstDyAMnth = -1111
    End Select
End Function

Tuesday, March 17, 2015

Cannot print exception string because Exception.ToString() failed

Just to report my case of the 'Exception.ToString() Failed' error.

I was constantly working on various tasks on my machine (Windows & x64) and there would be times that I would install patches and then roll them back. 

In this particular case, I was installing a bunch of MS updates in the hope to resolve an Excel macro errors. Without success, I rolled patches back while Windows insisted to install some of the patches( I believe that I specifically asked to roll back a batch of patches, but Windows will re-install some patches after rolled back either during the shut down or the re-boot phase). 

At this point, I found that my .NET 2.0 C# code stop working. I can still compile the code but the program will simply crash without much of a clue - the compilation worked since I was able to copy the updated dll to other machines and worked just fine. I ended up testing a console program with a single WriteLine() statement. The program crashed the same way with the message 'Cannot print exception string because Exception.ToString() failed'.

I googled and there were hints that pointed to the crashed Frameworks. Even though the .NET 2.0 was failed, I was able to compiled the same code in .NET 4.0 on the same machine and it worked.I then ran into articles that state that since Windows 7 was build on .NET 2.0 and 3.5, it is impossible to install the redistributable version on Windows 7.

At that point, I was convinced that the Framework was crashed and I tried the sfc.exe in vein to fixed the problem. There seems to have a Framework tool from MS that can perform some sort of repairs. I, however, decided to check into available .net framework patches and applied them. That did the trick and the Excel, too, worked.

Possible explanation?? The only thing I can think of is that, somehow, my roll back failed the .NET 2.0, which may due to Windows insisted on install certain patches.

Sunday, March 8, 2015

Failure rate on Banggood's Arduino Nano circuit board

Recently I began to experiment with Arduino - In my earlier life, I worked with electronics quite a bit.

This post is to report my experience with the failure rate of a particular product from a particular vendor - do not over interpret.

The product I evaluated is:
http://www.banggood.com/5Pcs-ATmega328P-Arduino-Compatible-Nano-V3-Improved-Version-With-USB-p-951782.html

The failure rate is 16% - 4 out of 25 Arduino nano board are bad.

* This board needs driver from:
    http://wch.cn/downloads.php?name=pro&proid=5

Problems ranging from:
1.  Unknown Device - PC recognized a USB device being attached but do not
      know what kind.

2.  No Action:
      PC does not even a USB device is being attached.
3.  Not in sync. PC recognized the Nano and allocated COM5. But when try to
      upload a program, I receive the error message:
          avrdude: stk500_getsync(): not in sync: resp=0x00

I will see what Banggood.com would do and report back. For now, just plan ahead and take the fail rate into your order and expecting 16% of your order could be bad.

========= Update 20150411 ========
Just got the replacement from Banggood. I am happy with their customer service.




Thursday, April 17, 2014

CS0246: The type or namespace name could not be found (are you missing a using directive or an assembly reference?)

This is a short note on the error message. I googled and none of those matched exactly what I did and none of them solved my problem.

Since each development is different and many situation can raise the same error message. It is, therefore, important to describe my setups in order to be useful to my blogger readers.

First of all, I am hand coding my code for the sack of purities. It should apply well to cases where more complicated environment is used.

I created a user control with code behind: .ascx and .cs. I then use this user control in my web page: MyPage.aspx and MyPage.cs. For page derived .cs files, you will need to declare variables for controls that you used in MyPage.aspx. I can declare those variables for all the standard web controls. However, when I declared variable for my user control, the error message result. I did employed namespace for my user control and I did 'using' that namespace. However, the IIS Express wouldn't recognize my control until I explicitly declare the variable with full-namespace specification.

As with my previous post concerning IIS Express, this problem could be just the IIS Express issues. Even though, some of the Internet posts may suggest otherwise.

Wednesday, April 16, 2014

The server tag is not well formed. ASP.NET IIS Express aspx ascx

When working with ASP.NET with IIS Express, I hand coded the user control .ascx file using the code behind approach.
For the <%@ Control > tag, the CodeBehind is for the .dll file - not the .cs source file. To use the source file, you will have to use the CodeFile attribute. To use the CodeBehind attribute, you also have to put the .dll file in the \bin sub-directory - using ~\file.dll in the CodeBehind will not work as other directives tags (i.e. ASP.NET @ tags).

There is a problem with IIS Express when parsing the XML comment (i.e. ). Somehow, the IIS Express will try to look for 'runat' inside the comment area and try to interpret it as server tag. This prevents commenting out server tags when needed. By altering the text: runat (e.g. runat_), you can avoid the problem caused by this bug. This problem applies to .aspx and .ascx files.


Friday, March 21, 2014

MS ACCESS 97 2002 upgrade 3264 TableDef error

Error message: "Run-time error '3264': No field defined--cannot append TableDef or Index."

Code at: EnblLnkTbl():CurrentDb().TableDef.Append tdf

When: upgrade my Ipds424.mdb (In ShrScr\ ) to ACCESS 2002.

Resolution: In the -Tool -Reference:
  Replace: 'Microsoft DAO 2.5/3.5 Compatibility Library'
  With: 'Microsoft DAO 3.6 Object Library'


Sunday, February 23, 2014

Windows Screen Recording Open Source software

Just few notes on my experiences with Windows open source Screen Recording Software.

When google it, Cam Studio came up top. Reading Wikipedia article was warned about viruses embedded in the Cam Studio. Found a website called LO4D.com and is safe to download from it. With Cam Studio, you can use encoder already installed on your Windows machine. Try to use the Microsoft one. Cam Studio stored recording in the AVI format which can be read by Microsoft Movie maker. By saving the video in NTSC format, you can upload it to YouTube.

I also played with Krut, which is based on Java and required install the normal JRE plus the JMF (java medial framework). The Krut worked well except that you do not have much choices on the encoder - it will output QuickTime format and a separate audio file in Microsoft format. The problem with the Krut is that when you record with 16 bit audio encoding, the VLC would complain and the audio would not play. The QuickTime seems to work fine. I suspect, you will need encoding software to make it more useful.

Another program I played is called rdp, which is also Java based. The program is at version 0.1. It worked by can't record Video and Audio at the same time yet.