Archive for the ‘VBA’ Category.

Using HTML5 Fractions in Exported Excel Tables

HTML5 provides for fractional representation of halves, thirds, fourths, fifths, sixths, no sevenths, and eighths. Excel has a fractional number format. This post is about bringing these concepts together for exporting an Excel table into Wiki or HTML designs. The basic representations are:

Name Hex Dec Result
½ U+000BD 189 ½
⅓ U+02153 8531
¼ U+000BC 188 ¼
⅕ U+02155 8533
⅙ U+02159 8537
⅛ U+0215B 8539
⅔ U+02154 8532
⅖ U+02156 8534
¾ U+000BE 190 ¾
⅗ U+02157 8535
⅜ U+0215C 8540
⅘ U+02158 8536
⅚ U+0215A 8538
⅝ U+0215D 8541
⅞ U+0215E 8542

 
The format for the name is &fracnd; where n is the numerator and d is the denominator. Thus ½ is a half, and ⅞ is seven-eighths. The HTML code representations for these are:

Result Named
Code
Hex
Code
Dec
Code
½ ½ ½ ½
⅓ ⅓ ⅓
¼ ¼ ¼ ¼
⅕ ⅕ ⅕
⅙ ⅙ ⅙
⅛ ⅛ ⅛
⅔ ⅔ ⅔
⅖ ⅖ ⅖
¾ ¾ ¾ ¾
⅗ ⅗ ⅗
⅜ ⅜ ⅜
⅘ ⅘ ⅘
⅚ ⅚ ⅚
⅝ ⅝ ⅝
⅞ ⅞ ⅞

 
In theory (more on “in practice” later) every representation in a row is equivalent. This is our test table to export to Wiki or HTML format:

D E F G H I J K L
1 1/1 1/2 1/3 1/4 1/5 1/6 1/7 1/8 1/80
2 2/1 2/2 2/3 2/4 2/5 2/6 2/7 2/8 2/8.
3 3/1 3/2 3/3 3/4 3/5 3/6 3/7 3/8 3/8A
4 4/1 4/2 4/3 4/4 4/5 4/6 4/7 4/8 6 4/8
5 5/1 5/2 5/3 5/4 5/5 5/6 5/7 5/8 7 5/8
6 6/1 6/2 6/3 6/4 6/5 6/6 6/7 6/8 8 6/8
7 7/1 7/2 7/3 7/4 7/5 7/6 7/7 7/8 9 7/8
8 8/1 8/2 8/3 8/4 8/5 8/6 8/7 8/8 10 8/8

 
Cell D1: =CHAR(32)&ROW()&”/”&COLUMN()-3, then fill down and right. The right hand column has a few test cases. I used that formulaic construction to keep Excel from doing the divisions. The Excel fractional format is “# ?/?” for single digit denominators. The intermediary space is important. It indicates that a fraction may follow, just as a forward slash indicates a fraction may be present. And a format of /?? is a fraction not translatable into HTML5. Turning these patterns into VBA, this is my MakeFracs() function. It checks that there is a slash, then that there is not a slash–digit–digit pattern, and finally that there is a “space–digits 1 through 7–slash–digits 2, 3, 4, 5, 6, 8″ pattern to screen out sevenths and ninths. If all of those pass, it substitutes in the &fracnd; formulation for the fraction.

Function MakeFracs(Arg As String) As String
   Dim sIN     As String
   Dim sOUT    As String
   Dim i As Long, j As Long
   Dim n As Long, d As Long
   Dim Fracs(1 To 15, 1 To 3) As String
   
   Fracs(1, 1) = "½": Fracs(1, 2) = "½": Fracs(1, 3) = "½"
   Fracs(2, 1) = "⅓": Fracs(2, 2) = "⅓": Fracs(2, 3) = "⅓"
   Fracs(3, 1) = "¼": Fracs(3, 2) = "¼": Fracs(3, 3) = "¼"
   Fracs(4, 1) = "⅕": Fracs(4, 2) = "⅕": Fracs(4, 3) = "⅕"
   Fracs(5, 1) = "⅙": Fracs(5, 2) = "⅙": Fracs(5, 3) = "⅙"
   Fracs(6, 1) = "⅛": Fracs(6, 2) = "⅛": Fracs(6, 3) = "⅛"
   Fracs(7, 1) = "⅔": Fracs(7, 2) = "⅔": Fracs(7, 3) = "⅔"
   Fracs(8, 1) = "⅖": Fracs(8, 2) = "⅖": Fracs(8, 3) = "⅖"
   Fracs(9, 1) = "¾": Fracs(9, 2) = "¾": Fracs(9, 3) = "¾"
   Fracs(10, 1) = "⅗": Fracs(10, 2) = "⅗": Fracs(10, 3) = "⅗"
   Fracs(11, 1) = "⅜": Fracs(11, 2) = "⅜": Fracs(11, 3) = "⅜"
   Fracs(12, 1) = "⅘": Fracs(12, 2) = "⅘": Fracs(12, 3) = "⅘"
   Fracs(13, 1) = "⅚": Fracs(13, 2) = "⅚": Fracs(13, 3) = "⅚"
   Fracs(14, 1) = "⅝": Fracs(14, 2) = "⅝": Fracs(14, 3) = "⅝"
   Fracs(15, 1) = "⅞": Fracs(15, 2) = "⅞": Fracs(15, 3) = "⅞"

   i = VBA.InStr(1, Arg, "/", vbTextCompare)
   If i = 0 Then   'there's no fraction
     MakeFracs = Arg
   ElseIf Mid$(Arg, i, 3) Like "/##" Then   'not HTML5
     MakeFracs = Arg
   ElseIf Mid$(Arg, i - 2, 4) Like " [1-7]/[234568]" Then
     sOUT = Mid$(Arg, i - 1, 3)
     n = VBA.Val(Left$(sOUT, 1))   'numerator
     d = VBA.Val(Right$(sOUT, 1))   'denominator
     If n < d Then
         If d Mod n = 0 Then
            d = d / n
            n = 1
         ElseIf d Mod 2 = 0 And n Mod 2 = 0 Then
            d = d / 2
            n = n / 2
         End If
         sIN = "&frac" & n & d & ";"
         For j = 1 To 15
            If Fracs(j, 1) = sIN Then
              sIN = Fracs(j, 2) '<-or Fracs(j, 3) for HEX
              Exit For
            End If
         Next j
         MakeFracs = VBA.Replace(Arg, sOUT, sIN)
      Else
         MakeFracs = Arg
      End If
   Else
      MakeFracs = Arg
   End If

End Function

 
At least that’s all I wanted it to do. In practice, Wikipedia and WordPress seem to be not fully onboard with HTML5 and do not handle all fifteen &fracnd; formats (I confirmed Firefox does). That added “j-loop” in the middle translates the &fracnd;’s into Dec code. This works fine, though it’s a step back from HTML5. Option is given to use Hex if desired. Your Excel table then looks like this:

N O P Q R S T U V
1 1/1 &#189; &#8531; &#188; &#8533; &#8537; 1/7 &#8539; 1/80
2 2/1 2/2 &#8532; &#189; &#8534; &#8531; 2/7 &#188; &#188;.
3 3/1 3/2 3/3 &#190; &#8535; &#189; 3/7 &#8540; &#8540;A
4 4/1 4/2 4/3 4/4 &#8536; &#8532; 4/7 &#189; 6 &#189;
5 5/1 5/2 5/3 5/4 5/5 &#8538; 5/7 &#8541; 7 &#8541;
6 6/1 6/2 6/3 6/4 6/5 6/6 6/7 &#190; 8 &#190;
7 7/1 7/2 7/3 7/4 7/5 7/6 7/7 &#8542; 9 &#8542;
8 8/1 8/2 8/3 8/4 8/5 8/6 8/7 8/8 10 8/8

 
Where N1: =MakeFracs(D1) filled down and right. Arranged that way you can see the HTML5 design thoughts. The very ugly website would look like this:

1/1 ½ ¼ 1/7 1/80
2/1 2/2 ½ 2/7 ¼ ¼.
3/1 3/2 3/3 ¾ ½ 3/7 ⅜A
4/1 4/2 4/3 4/4 4/7 ½ 6 ½
5/1 5/2 5/3 5/4 5/5 5/7 7 ⅝
6/1 6/2 6/3 6/4 6/5 6/6 6/7 ¾ 8 ¾
7/1 7/2 7/3 7/4 7/5 7/6 7/7 9 ⅞
8/1 8/2 8/3 8/4 8/5 8/6 8/7 8/8 10 8/8

 
Frankly, I’m not sure that this is an improvement. You’ll come across Wikipedia editors, however, who are convinced of it. I thought about adding a trailing space, as in MakeFracs = VBA.Replace(Arg, sOUT, sIN & Chr(32)), but for every time I wanted to, I thought of an example where I didn’t, and the logic got very convoluted. Better I decided to put the space in the table where wanted and not in the function. My HTML tablemaker is here, but it’s being overcome by the hard steady march of technology. Wiki and CSS tablemakers are coming up. I used MakeFracs() in the above. No fractions were harmed in the making of this post.

…mrt
©¿©¬

Populating Class Properties

Depending on the requirements, I’ll choose a method for populating my custom collection classes using from data from a worksheet.

In this example, I’m using a list of the best selling albums of all time.

My Album class has properties as follows:

Public Artist As String
Public Album As String
Public Released As Date
Public Genre As String
Public Sales As Long

The code in my main routine is:

Sub test()
    Dim albs As Albums, alb As Album
 
    Set albs = New Albums
    albs.FillFromSheet Sheet1
End Sub

Filling the collection is just a matter of reading each row and popping the contained values into the right property.
The difficulty is knowing which columns relate to what properties. It’s a mapping problem – mapping columns to properties.

I could make an assumption about the positions of the columns and assume each is a known index.

Public Sub FillFromSheet(wks As Worksheet)
    Const cFirstRow = 2
    Dim i As Long, obj As Album
 
    With wks
        For i = cFirstRow To .Cells(Rows.Count, 1).End(xlUp).Row
            Set obj = New Album
            obj.Artist = .Cells(i, 1)
            obj.Album = .Cells(i, 2)
            obj.Released = .Cells(i, 3)
            obj.Genre = .Cells(i, 4)
            obj.Sales = .Cells(i, 5)
 
            Me.Add obj
        Next
    End With
End Sub

I don’t really like this because I’ve been taught and have tried to follow the approach that offsets/indices like these should be defined as constants.
I’ll modify my FillFromSheet code.

Const cArtistCol = 1, cAlbumCol = 2, cReleasedCol = 3, cGenreCol = 4, cSalesCol = 5
...
    obj.Artist = .Cells(i, cArtistCol)
    obj.Album = .Cells(i, cAlbumCol)
    obj.Released = .Cells(i, cReleasedCol)
    obj.Genre = .Cells(i, cGenreCol)
    obj.Sales = .Cells(i, cSalesCol)

Seems roughly better, but this too has problems. It can be a pain when you want to change the column order or insert a new column.
You’d have to go through the code and update all of the numbers.
While this is ok for 5, as I have here, changing out 50 columns is a chore.

So, I use an Enum block to determine column positions.
At the top of the class module:

Private Enum AlbumCols
    Artist = 1  ' =1 is necessary, otherwise Enum starts at zero
    Album
    Released
    Genre
    Sales
End Enum

And another modification to my FillFromSheet code:

    obj.Artist = .Cells(i, AlbumCols.Artist)
    obj.Album = .Cells(i, AlbumCols.Album)
    obj.Released = .Cells(i, AlbumCols.Released)
    obj.Genre = .Cells(i, AlbumCols.Genre)
    obj.Sales = .Cells(i, AlbumCols.Sales)

That works well. If I rearrange my columns on the worksheet, the only code change needed is a swap of items in the Enum block – a painless and quick update!

But that’s only if the changes to column order are in my control.
What if an end user changes the column order? Do I really want them poking around in my Enum code too?
I’d usually stop now and decide that if the end user starts screwing with column positions, it’s fair enough that the code should break.
However, if I wanted to go the extra mile, I’d have to find the column index by searching for the text in the column headers.

Excel 2007 (and 2003 to an extent) has a feature called Tables, otherwise known as ListObjects.

My code in the main routine changes to:

Set albs = New Albums
albs.FillFromTable Sheet1.ListObjects("Best_Selling_Albums")

… and the code in my collection class is:

Public Sub FillFromTable(tbl As ListObject)
    Const cArtistCol = "Artist", cAlbumCol = "Album", cReleasedCol = "Released"
    Const cGenreCol = "Genre", cSalesCol = "Sales (millions)"
 
    Dim i As Long, obj As Album, row As ListRow, col As New Collection
 
    With tbl.HeaderRowRange: For i = 1 To .Count: col.Add i, .Columns(i): Next: End With
 
    For Each row In tbl.ListRows
        Set obj = New Album
        obj.Artist = row.Range(, col(cArtistCol))
        obj.Album = row.Range(, col(cAlbumCol))
        obj.Released = row.Range(, col(cReleasedCol))
        obj.Genre = row.Range(, col(cGenreCol))
        obj.Sales = row.Range(, col(cSalesCol))
 
        Me.Add obj
    Next
End Sub

In the preceding code I created a collection of key-value pairs. The key is the column label, the value is column index.
I use that as a lookup when populating my properties.

That’s just a few approaches to reading a table of values into a collection.
I’m interested in feedback. How do you map column positions to properties?

Finding Almost This Friday

I have a utility that determines the date of the upcoming Friday. It used to look like this

dtReturn = Date + 8 - Weekday(Date, vbFriday)

It has served me well for many years. Until this year. This year, the US celebrated Veterans Day on November 11. While I’m work, the bankers are all at home rolling around in their money. Since my bank would not process any ACH transactions that day, I paid my employees the preceding Thursday. But my function didn’t say “return this Friday unless it happens to be a banking holiday” but now it does.

Public Function ThisFriday() As Date
   
    Dim dtReturn As Date
    Dim i As Long
   
    'if i'm testing, hardcode the date
   If Len(Dir(gsDEBUGDATE)) > 0 Then
        Stop
        dtReturn = #2/18/2011#
    Else
        'return the next friday
       dtReturn = Date + 8 - Weekday(Date, vbFriday)
       
        'if there are no checks on that date, look for
       'a nearby date with checks
       If Not gclsEmployees Is Nothing Then
            If Not gclsEmployees.HasChecks(dtReturn) Then
                For i = 1 To 7
                    If gclsEmployees.HasChecks(dtReturn + i) Then
                        dtReturn = dtReturn + i
                        Exit For
                    ElseIf gclsEmployees.HasChecks(dtReturn - i) Then
                        dtReturn = dtReturn - i
                        Exit For
                    End If
                Next i
            End If
        End If
    End If
   
    ThisFriday = dtReturn
   
End Function

And the HasChecks property is just a loop

Public Property Get HasChecks(dtCheck As Date) As Boolean
   
    Dim bReturn As Boolean
    Dim clsEmployee As CEmployee
   
    bReturn = False
   
    For Each clsEmployee In Me
        If Not clsEmployee.CheckByDate(dtCheck) Is Nothing Then
            bReturn = True
            Exit For
        End If
    Next clsEmployee
   
    HasChecks = bReturn
   
End Property

Man I hate when real life gets in the way of good code. I need to get rid of that arrow antipattern, but otherwise I’d say this is solved for all time. Jinx.

Procedure Flow and Raising Errors

I have a pretty simple procedure that I run a few times every other week. Someone emails their timesheet to me, I open the timesheet in Excel, and I want a quick way to save it to the proper directory. GMail stores the timesheet in my Temp directory and navigating through the file structure is a pain. I wrote this procedure and it includes the following conditions:

  • Is there an active workbook
  • Is that workbook a timesheet
  • Has the timesheet already been saved
  • Does the path exist

That results in four levels of nested ‘If’ statements. I like nested statements. I think it makes the code very readable and easy to follow.

Public Sub SaveDownloadedTimesheet()
   
    Dim wb As Workbook
    Dim dtThisFriday As Date
    Dim sNewPath As String
    Dim sNewName As String
   
    Const sPAYROLLPATH As String = "\\Server\Share\Accounting\Payroll\Processing\"
    Const sCOPY As String = "Copy of "
       
    On Error Resume Next
        Set wb = ActiveWorkbook
    On Error GoTo 0
   
    If Not wb Is Nothing Then
        If IsTimesheet(wb) Then
            If IsTemp(wb) Then
                dtThisFriday = ThisFriday
                sNewPath = sPAYROLLPATH & Format(dtThisFriday, "mmdd") & "\"
               
                If Len(Dir(sNewPath, vbDirectory)) > 0 Then
                    sNewName = Replace(wb.Name, sCOPY, "", 1, 1)
                    wb.SaveAs sNewPath & sNewName
                    wb.Close False
                End If
            End If
        End If
    End If
   
End Sub

The code works great except when it doesn’t. When there’s a problem, I don’t get any kind of message telling which one of my conditionals failed. It’s usually that the path doesn’t exist, but today I was absolutely freaking positive that the path existed and it still wasn’t saving. Of course I was wrong, but that’s not the point.

One solution is to add an Else clause to each If statement with a message box. There’s nothing wrong with that solution, but I don’t find it aesthetically pleasing. Did I just say “aesthetically pleasing”? My goodness. It adds eight lines of code and, to me, ruins the flow of the program.

An alternative is to raise errors. That might look like this:

Public Sub SaveDownloadedTimesheet()
   
    Dim wb As Workbook
    Dim dtThisFriday As Date
    Dim sNewPath As String
    Dim sNewName As String
   
    Const sPAYROLLPATH As String = "\\Server\Share\Accounting\Payroll\Processing\"
    Const sCOPY As String = "Copy of "
       
    On Error GoTo ErrHandler
   
    On Error Resume Next
        Set wb = ActiveWorkbook
    On Error GoTo ErrHandler
   
    If wb Is Nothing Then Err.Raise 9999, , "No active workbook"
    If Not IsTimesheet(wb) Then Err.Raise 9999, , "Active workbook is not a timesheet"
    If Not IsTemp(wb) Then Err.Raise 9999, , "Timesheet already saved"
   
    dtThisFriday = ThisFriday
    sNewPath = sPAYROLLPATH & Format(dtThisFriday, "mmdd") & "\"
   
    If Len(Dir(sNewPath, vbDirectory)) = 0 Then Err.Raise 9999, , "Can't find folder for " & Format(dtThisFriday, "mmdd")
   
    sNewName = Replace(wb.Name, sCOPY, "", 1, 1)
    wb.SaveAs sNewPath & sNewName
    wb.Close False
   
ErrExit:
    Exit Sub
   
ErrHandler:
    MsgBox Err.Description
    Stop
    Resume
   
End Sub

I don’t like that as much as nicely nested Ifs, but I like it better than a bunch of Else clauses. Most of my conditions are nicely grouped, so it’s not difficult to see what I’m checking. And it tells me the problem, so that’s nice. Which do you prefer? Do you use a different method?

Get a Table from a Web Page with an XML Request

I’m tired of trying to remember the library names and the syntax for doing this. I’m putting here so when I search for XMLRequest, Web Request, HTTPRequest, I can find it.

Sub GetData()

    Dim oHttp As MSXML2.XMLHTTP
    Dim sHtml As String
    Dim hDoc As HTMLDocument
    Dim hTable As HTMLTable
    Dim hRow As HTMLTableRow
    Dim hCell As HTMLTableCell
    Dim rStart As Range
           
    Const sURL As String = "http://www.contextures.com/xlsampledata01.html"
   
    Set oHttp = New MSXML2.XMLHTTP
    Set hDoc = New HTMLDocument
    Set rStart = Sheet1.Range("A1")
   
    'Send the web request
   oHttp.Open "GET", sURL
    oHttp.send
   
    'Give it enough time to process
   Do
        DoEvents
    Loop Until oHttp.readyState = 4
   
    'put the web page into an HTML Document
   hDoc.body.innerHTML = oHttp.responseText
   
    'Find the right table and write it to a sheet
   For Each hTable In hDoc.all.tags("TABLE")
        If hTable.Rows(0).Cells(0).innerText = "OrderDate" Then
            For Each hRow In hTable.Rows
                For Each hCell In hRow.Cells
                    rStart.Offset(hRow.RowIndex, hCell.cellIndex).Value = hCell.innerText
                Next hCell
            Next hRow
        End If
    Next hTable
   
End Sub

It sends a request to the sample data page over at Contextures, jams that into an HTML Doc, then writes the table out to a sheet.

A Case for Value as a Default Property

Charles Williams had an interesting post1 the other day about the Text, Value, and Value2 properties of the Range object. In it, he wrote:

So .Value2 really should be the default, and is definitely the one to use 99% of the time.

I disagree. I never use Value2.

The Value2 property returns a Double data type (64-bit floating point). The Value property returns the same thing except when the cell is formatted as Date or Currency. When it’s currency, Value returns a Currency data type (64-bit scaled integer), and when it’s a date, Value returns a Date date type (another 64-bit floating point). Who cares (besides Charles and me) and what does it mean?

Let’s imagine that we’re writing our own spreadsheet program to get a better understanding of what Excel is doing. I don’t actually know what Excel does under the hood, so this is all conjecture. In our spreadsheet application that we’re writing from scratch, we’ll store all numbers in memory as IEEE double precision floating point number, or Doubles. There is a case when the user may want more calculation accuracy than floating point allows and is willing to sacrifice a little precision (I’m looking at you fellow accountants). To offer that feature, we’ll allow the user to identify certain numbers as scaled integers with 15 digits to the left of the decimal and four digits to the right. Let’s call that data type Currency. We’re still going to store the value as a Double, but we’ll do the conversion prior to any calculations to ensure the accuracy.

As spreadsheet writers, we have a decision to make. I said we’d allow the user to identify certain numbers as Currency, but how exactly do they identify it. We could add a property called DataType to the Range class. OK, but how does the user set that property? Most Range properties are set in the Format Cells dialog box. We have a few tabs on that dialog already, so let’s add another one called Data Type. The user can set the data type and we’ll do the floating point to scaled integer conversion when the DataType property is set to Currency. That’s when the usability people come in.

The usability people begin by hurling epithets at us regarding our lack of sex lives and penchant for role playing games. They say that normal people (i.e. non-programmers) don’t have any idea what a “data type” is and if we make them learn, our sales will go down by 14.1% (a totally made up number because marketing people can’t do math). Surely, they go on, there is a better way to identify Currency values. Then they get out the corporate programming guidelines and remind us that no dialog box can have more than six tabs. We already have six tabs on the Format Cells dialog, so we can’t add another (Tools – Options hasn’t been invented yet).

Time to compromise. One of our bright, young interns suggests that we make Currency a format. As programmers, we are incensed. We already have an Accounting format and a Currency format will be confusing. And besides, Currency is a data type, not a format. Due to our lack of persuasiveness or the caffeine withdrawals, we agree to add it is a format. Because of the Currency debacle we go from two levels to three levels for data display. The bottom level is the raw Double that’s stored in memory, just like before. The top level is the text representation of the number with all the commas and periods and such, just like before. We need to add a pre-processor level. At this level, we’ll check to see if the Range object has it’s NumberFormat property set to Currency and, if so, we’ll convert from floating point to scaled integer. Once converted, we’ll send to the formatting layer to add the text goodies.

The deed is done and we all feel a little dirty. We code the changes and send them over to the VBA folks. The guy building the object model already has a Value property and a Text property. Now that we’ve added another layer, he doesn’t know which value to return for the Value property. There’s really not much debate – it has to be the value that comes from the pre-processor. If people “format” a value as Currency, they’re going to expect a scaled integer from the Value property even if they don’t know what the hell a scaled integer is. Object model guy then asks if he should expose the raw value. Of course. We’re all about exposing ourselves. We call in the dullest intern on staff to come up with the name and, true to form, he produces Value2. Positively inspired.

And scene.

You may disagree with some of the decision made in the above dramatic reenactment, but you have to realize they’re all connected. I personally disagree with calling Currency a format. But whether we call it format or a data type, doesn’t change the fact that we have to convert it. You can disagree with the decision to store all numbers as Doubles, and just store Currency as a scaled integer in memory. That doesn’t really change how the user identifies Currency values and you can’t simply store everything as currency because then everything would be slow.

Back to my assertion that Value is the appropriate default (or, put another way, that Value points to the right layer). I contend that if you “format” something as Currency or Date, that you do so knowingly and for a specific purpose. One of the knocks on Value is that you lose precision for Currency because Currency only goes four digits out to the right. I say you don’t lose precision. The precision that’s there is a myth. It’s only there because we had to convert the number to a float. When we chose Currency, we consciously forfeited all rights to precision beyond the 10,000th place (or should have). The fact that we can convert that number to a Double and see what looks like precision, doesn’t make it there.

For the Date type, I have a different argument. It’s not that there’s any problem with the data manipulation when converting from Double to Date, it’s just that it takes longer. If I’m reading in a date and spitting it back out, I agree that Value2 is probably the better choice. However, if that’s all I’m really doing I might want to address what value I’m adding. In most cases, I’m manipulating the date in such a way that I care that it’s a date. What I don’t want to do is read in a double using Value2 (avoiding the conversion overhead), then have to convert that number to a date myself to manipulate it, then convert it back to a double. In almost all cases, I want a Date or Currency typed number when I “format” it that way.

I suspect that most people who prefer Value2, in fact, disagree with the decision to confuse formatting and data types in the user interface. And if so, then we agree on that. Let me see if I can reword part of Charles’ statement so that we both agree with it: When performance matters, Value2 should be used 100% of the time there are no dates or currency, and Value should be used 100% of the time there are. I wonder if that will fly.

Footnotes
1In fact, all of Charles’ posts are interesting, so if you’re not subscribed to his RSS feed2 yet, get on it.
2If you’re still not reading blogs via RSS, be sure to say hello to my mom at the next Rotary meeting.

UDF for Cumulative Sum

Back in my newsgroup days, about once a month someone would ask how to have a formula that accumulated entries from another cell. For example, put a formula in C3 that refers to A3. Every time a value is entered in A3, have the value in C3 change by that amount. Impossible right?

The standard answer is that you can’t and that you should use a Worksheet_Change event to monitor A3. When it changes, change the value in C3. Then I read Charles Williams’ comments about the Text property

When used inside a UDF you can use .Text to get the formatted value of the calling cell as it was before the UDF was called (you can’t do this with .Value or .Value 2 without causing a circular reference).

I had no idea that was true. Now I can write a UDF like this:

Function UpDown(dChange As Double) As Double
   
    Dim sOld As String
   
    Application.Volatile True
   
    sOld = Application.Caller.Text
   
    UpDown = Val(sOld) + dChange
   
End Function

In C3, I put =updown(A3). Whenever I change A3, C3 changes by that amount. I’m sure there are all kinds of problems with this – dates and errors to name two – but I thought it was interesting nonetheless. Apologies if the entire Excel universe knows this already, but it’s new to me.