Search and remove "00:00" values from each column - excel

I am trying to get my codes to go through each DateTime column and search for cells that contain "00:00" as hours and minutes. If the value exists, remove 00:00 and only leave the date. For the cells that don't contain that value, leave the cells as they are. For example, if "3/22/2017 00:00", then format it to "3/22/2017". Otherwise, if "3/22/2017 09:16", leave it alone. Thank you in advance!
dataGrid.DataSource = dataSet.Tables(0)
dataGrid.DataBind()
ws.Cells(1, 1).LoadFromDataTable(dataGrid.DataSource, True)
Dim data = dataGrid.DataSource
Dim columnCount = data.Columns.Count
For i = 0 To columnCount - 1
If data.Columns(i).DataType Is GetType(DateTime) Then
If Not data.Columns(i).ToString.Contains("00:00") Then
ws.Column(i + 1).Style.Numberformat.Format = "mm/dd/yyyy hh:mm"
Else
ws.Column(i + 1).Style.Numberformat.Format = "mm/dd/yyyy"
End If
End If
Next

For your solution to work, you would need to set the style on each individual cell. This is an inefficient solution as a style would be created for each cell. Is could be mitigated somewhat by defining two different styles and assigning those references to the cell as needed.
A simpler solution is use the DataGridView.CellFormatting Event to set the format when the cell is painted.
Evidently, the DateTime values have a time component less than one minute because the default formatting would have yield the desired result if this was not the case. The code below sets the format based on whether the time component is less than one minute.
Private Shared tsLimit As New TimeSpan(0, 1, 0) ' 1 minute
Private Shared dtType As Type = GetType(DateTime)
Private Sub dataGrid_CellFormatting(sender As Object, e As DataGridViewCellFormattingEventArgs) Handles dataGrid.CellFormatting
Dim dgv As DataGridView = CType(sender, DataGridView)
Dim dt As DataTable = TryCast(dgv.DataSource, DataTable)
If dt IsNot Nothing Then
Dim sourceColumn As DataColumn = dt.Columns.Item(dgv.Columns.Item(e.ColumnIndex).DataPropertyName)
If sourceColumn IsNot Nothing AndAlso sourceColumn.DataType Is dtType AndAlso e.Value IsNot Nothing AndAlso Not e.Value Is DBNull.Value Then
Dim d As DateTime = CDate(e.Value)
If d.TimeOfDay < tsLimit AndAlso d.Hour <> 0 Then
e.CellStyle.Format = "mm/dd/yyyy"
Else
e.CellStyle.Format = "mm/dd/yyyy hh:mm"
End If
End If
End If
End Sub

This can be accomplished by:
Setting a default format for all DateTime columns
Only overriding the default format for values that meet your criteria.
Here's a simple helper method that handles multiple DateTime columns:
Sub SetDateTimeStyles(ByRef data As DataTable, ByRef ws As ExcelWorksheet)
' Track DateTime columns to override default column style
Dim dateTimeColumns = New List(Of Integer)()
' Set column format
Dim columnCount = data.Columns.Count
For i = 0 To columnCount - 1
If data.Columns(i).DataType Is GetType(DateTime) Then
Dim epPlusColumn = i + 1
ws.Column(epPlusColumn).Style.Numberformat.Format = "mm/dd/yyyy hh:mm"
dateTimeColumns.Add(epPlusColumn)
End If
Next
' Header row exists; set to 1 if no header row
Dim rowOffset = 2
Dim rowCount = data.Rows.Count
' Only set cell format when hour and minute are **both** zero
For i = 0 To rowCount - 1
For Each dateTimeColumn In dateTimeColumns
Dim value As DateTime = data(i)(dateTimeColumn - 1)
If value.Hour = 0 AndAlso value.Minute = 0 AndAlso value.Second = 0 Then
ws.Cells(i + rowOffset, dateTimeColumn) _
.Style.Numberformat.Format = "mm/dd/yyyy"
End If
Next
Next
End Sub
And call it when creating the Excel file:
dataGrid.DataSource = dataSet.Tables(0)
dataGrid.DataBind()
ws.Cells(1, 1).LoadFromDataTable(dataGrid.DataSource, True)
Dim data As DataTable = dataGrid.DataSource
SetDateTimeStyles(data, ws)
Result:

Related

Coloring Excel rows

So i found this script on this site to color rows with the same cell-data and change the color when the celldata changes and it seems to work just fine, but i have two minor issues
It seems to only apply to the first 900 rows (I have an excel list with 8000+ rows)
It colors the entire row, is there a way to make it only color a certain part of the row?
Thanks in advance! here's the script:
Public Sub HighLightRows()
Dim i As Integer
i = 2 'start at 2, cause there's nothing to compare the first row with
Dim c As Integer
c = 2 'Color 1. Check http://dmcritchie.mvps.org/excel/colors.htm for color indexes
Do While (Cells(i, 1) <> "")
If (Cells(i, 1) <> Cells(i - 1, 1)) Then 'check for different value in cell A (index=1)
If c = 2 Then
c = 37 'color 2
Else
c = 2 'color 1
End If
End If
Rows(Trim(Str(i)) + ":" + Trim(Str(i))).Interior.ColorIndex = c
i = i + 1
Loop
End Sub
Try this:
Public Sub HighLightRows()
Const START_ROW As Long = 2 '<< use a Constant for fixed values
Const VALUE_COL As Long = 1
Dim rw As Range, emptyCells As Long, i As Long, currentValue, tmp
Dim arrColors
arrColors = Array(37, 2)
Set rw = ActiveSheet.Rows(START_ROW)
currentValue = Chr(0) 'dummy "current value"
Do While emptyCells < 10 'quit after 10 consecutive empty cells
tmp = rw.Cells(VALUE_COL).Value
If Len(tmp) > 0 Then
If tmp <> currentValue Then
i = i + 1
currentValue = tmp 'save the new value
End If
'assign the color to a specific set of cells in the row
' starting at cell 1 and 5 columns wide
rw.Cells(1).Resize(1, 5).Interior.ColorIndex = arrColors(i Mod 2)
emptyCells = 0 'reset empty row counter
Else
emptyCells = emptyCells + 1 'increment empty row counter
End If
Set rw = rw.Offset(1, 0) 'next row
Loop
End Sub
It looks like the code only evaluates if the cell is the same as the cell above it. Conditional formatting, as John Coleman said, would be more effective. With it values in the whole column can be evaluated instead of just adjacent ones. And, if I'm not mistaken, there's a setting to look for dup values since Excel 2007, so there doesn't have to be some kind of formula kung-fu to do it.
Unless I'm missing something, it's as simple as Conditional Formatting -> Highlight Cell Rules -> Duplicate Values.

Looping over each vba collection failing conflicting variable types

I'm new to VBA and am picking up pieces. I'm having a problem understanding the collection created and then looping over it to read the values against each key one at a time. My code is below.
Excel Data I'm using with the data
Suffice to say, the error I get in Excel 2016 when running the module is:
Pressing Debug shows
I'm trying to print the values against the keys. I expected 80, 20 etc.. to be printed. Could someone please help me to understand why I'm wrong in writing dataItems and how to resolve so it prints the values agains the keys - I suspect it's a for loop that's needed.
Any help would be appreciated.
Code I'm using:
Class : CItems
Option Explicit
Public Key As String
Public Sum As Long
Public Count As Long
Public ItemList As Collection
Public Function Mean() As Double
Mean = Sum / Count
End Function
Private Sub Class_Initialize()
Sum = 0
Count = 0
Set ItemList = New Collection
End Sub
Module: m_Call
Option Explicit
Sub m_Call()
''' Create Collection from Column A and B in worksheet called RAW_DATA
Dim col As Collection
Dim dataItems As cItems
Dim itemKey As String
Dim item1 As Long
Dim ws As Worksheet
Dim r As Long
Set ws = ThisWorkbook.Worksheets("RAW_DATA")
Set col = New Collection
For r = 2 To 3000
itemKey = CStr(ws.Cells(r, "A").Value2) '~~adjust to your own column(s)
item1 = CLng(ws.Cells(r, "B").Value2) '~~adjust to your own column(s)
'Check if key already exists
Set dataItems = Nothing: On Error Resume Next
Set dataItems = col(itemKey): On Error GoTo 0
'If key doesn't exist, create a new class object
If dataItems Is Nothing Then
Set dataItems = New cItems
dataItems.Key = itemKey
col.Add dataItems, itemKey
End If
'Add cell values to the class object
With dataItems
.Sum = .Sum + item1
.Count = .Count + 1
.ItemList.Add item1
End With
Next
'Iterating through all of the items
Dim i As Long
i = 5
For Each dataItems In col
Debug.Print dataItems.Mean
ws.Cells(5, i) = dataItems.Key
' read in column 5 and check search each cells content to see if it matches a collection key's string.
i = i + 1
Next
'Selecting one item
'Set dataItems = col("PersonA 1")
'ws.Cells(4, 5) = dataItems.Mean
''' Read excel and populate categories if the value in a column A cell matches with a key in the Collection.
''' Column 10 and 11 should have the values that match each Key inserted respectively.
Dim cols As Range
Dim rng As Range
Dim currentRow As Long
Dim category As Variant
' Use a With block to 'Fully Qualify' the ranges to MySheet
With ThisWorkbook.Sheets("RAW_DATA")
' Set col range to the intersection of used range and column B
Set cols = Intersect(.UsedRange, .Columns("A"))
' Loop through cells in cols to set description in each row
For Each rng In cols
currentRow = rng.Row
' Read in key's from collection
For Each dataItems In col
' read in column and search each cells content to see if it matches a collection key's string.
.Cells(currentRow, 10) = rng.Value
If rng.Value = dataItems.Key Then
.Cells(currentRow, 10) = "Working"
'Debug.Print dataItems
'''Need to insert value1 from key into Column 10 and value2 from same key into column 11.
''' I'm just testing to see if I can insert a single category first before working on the loop.
.Cells(currentRow, 10) = "Shopping"
.Cells(currentRow, 11) = dataItems
End If
Next
Next rng
End With
''' End of Read excel
End Sub
I really cannot get what you want to achieve. Also, I have the vague idea that you are overthinking your problem. With those concerns in mind, try the following and see if it helps you.
If rng.Value = dataItems.Key Then
.Cells(currentRow, 10) = "Working"
'Debug.Print dataItems
'''Need to insert value1 from key into Column 10 and value2 from same key into column 11.
''' I'm just testing to see if I can insert a single category first before working on the loop.
.Cells(currentRow, 10) = "Shopping"
For k = 1 To dataItems.Count
.Cells(currentRow, k + 11) = dataItems.ItemList(k)
Next
End If
Also, try to use the Watch Window, adding dataItems as the inspected variable. Insert a Breakpoint in your code (for example in If rng.Value = dataItems.Key Then), and step on with F8.

How to hide multiple columns at once in Excel 2007 using vba

I would like to hide multiple columns inside an excel worksheet. This works pretty fine using:
ActiveSheet.Range("R10:CO10").EntireColumn.Hidden = True
"R10" is the first and fix column to hide. The second column and all columns between to hide will be dynamically determined depending on it´s date value.
Sample Coding:
Private Sub Worksheet_Activate()
Dim c As Range
Dim start As String
Dim ende As String
start = "R10"
ende = "CO10"
Dim d As Date
d = Date
For Each c In Range("R10:HU10")
If c = (d - 8) Then
ende = **how to assign???**
End If
If c = (d - 7) Then
Application.Goto c, True
End If
Next c
'ActiveSheet.Range(**"start:ende"**).EntireColumn.Hidden = True
End Sub
Row 10 holds date values and I would like to hide all columns which dates are older than 7 days and I can´t find any hints about hiding multiple columns using variables or with column identifier or the number of the column etc.
The use of variables ends up in runtime error 1004.
As Scott pointed out, my first answer was not complete. You can use the following function:
Function GetColChars(col As Integer) As String
Dim coldown As Integer 'Column Countdown
Dim colrem As Integer 'Coumn Value Remaining
Dim colname As String 'Temporary String value for column name
Const alphanums = 26
Const aposition = 64
coldown = col
colname = ""
While coldown > 0
colrem = coldown Mod alphanums
If colrem = 0 Then colrem = 26
newchar = Chr(64 + colrem)
colname = newchar & colname
coldown = Int((coldown - 1) / alphanums)
Wend
GetColChars = colname
End Function
Then call the function to get the column letters:
ende = GetColChars(c.column)
I have tested Scotts solution approach and finally got it working. In that case that some other people will struggle with the same problems, getting a working solution, here is my solution:
Private Sub Worksheet_Activate()
Dim lastDateRangeColumn As Range
Dim givenDateRange As Range
Set givenDateRange = ActiveSheet.Range("R10:HU10")
Dim firstDateRangeColumn As Range
Set firstDateRangeColumn = ActiveSheet.Range("R10")
Dim todaysDate As Date: todaysDate = Date
For Each tempDateRangeColumn In givenDateRange
If tempDateRangeColumn < (todaysDate - 7) Then
Set lastDateRangeColumn = ActiveSheet.Range(tempDateRangeColumn.Address)
End If
If tempDateRangeColumn = (todaysDate - 7) Then
Application.Goto tempDateRangeColumn, True
End If
Next tempDateRangeColumn
Dim firstColumnNumber As Long
Dim lastColumnNumber As Long
firstColumnNumber = Range(firstDateRangeColumn.Address).Column
lastColumnNumber = Range(lastDateRangeColumn.Address).Column
Dim rangeToBeHidden As Range
Set rangeToBeHidden = Range(Cells(1, firstColumnNumber), Cells(1, lastColumnNumber))
rangeToBeHidden.EntireColumn.Hidden = True
End Sub

Excel VBA Writing Multiple Scenario Using If Conditional Statement

wk2LastRow = wk2.Cells(Rows.Count, 1).End(xlUp).Row
i = 2
For wk2Range = 2 To wk2LastRow
id = wk2.Cells(wk2Range, 1)
Set f = wk1.Range("A2:I7").Find(id, , xlValues, xlPart)
'Conditional Statement code to be inserted here
Next wk2Range
Background information:
Search values are in Worksheet 2 (wk2).
wk2Range selects the entire search value
"A2:I7" are the values below from 1 to 6
I want to match a list of values to the "A2:I7" (1 to 6). The data that I need from the table is the second OFF day's Date. For example, For name 2, the second off day dates are 01/02/15, 01/06/15.
I want the code to find a match, afterwards read the code horizontally to find the second off day, and then end(xlup) to get the date.
I have thought of the 4 possible scenarios but am unsure of how to write them down into code. I just need the second off days date, the code can ignore the rest of the variations.
On On Off Off
On Off Off On
Off Off On On
Off On On Off
Please see picture for elaboration:
This is not the most elegant solution but should return the dates you are hoping for:
Private Sub ViewHolidayDates()
'Defined rota range:
Set Rota = Sheet1.Range("B2:I7").Cells
'How many dates across.
Dim RowLength As Integer: RowLength = 8
'Which Name we are looping.
Dim Row As Integer: Row = 1
'Where within the range loop we are.
Dim UserRecord As Integer: UserRecord = 0
'Current count of "off" for current Row.
Dim Holiday As Integer: Holiday = 0
Dim DateValue As String
For Each Record In Rota
If Record = "off" Then
Holiday = Holiday + 1
If Holiday = 2 Then
DateValue = Sheet1.Range("B2").Offset(-1, UserRecord).Value
MsgBox (DateValue)
'reset Holiday counter:
Holiday = 0
End If
End If
UserRecord = UserRecord + 1
If UserRecord = RowLength Then
'Reset counters ready for new "Name"
Row = Row + 1
UserRecord = 0
Holiday = 0
End If
Next Record
End Sub
Please set the Rota variable in my code to the range in your Excel Sheet and also the Sheet1.Range("B2") in my code to be the first record in your range.
UPDATE:
You can adjust the RowLength and the If Holiday = 2 to values more suited to your scenario. You should also note "off" is case sensitive.
UPDATE
I have updated the Holiday counter to reset within the IF statement, this will then return a date for each instance of the second "off".
Also, my Row - Row - 1 was not required, as the range I defined was fixed: Sheet1.Range("B2") we need only to say Offset(-1, UserRecord) and dynamically pass in how many columns across we expect the date to be.
Here's one way to do it, just assign SecondOff to the range of your choice in the calling sub. Note that there are probably better ways to do it than with Find, but I wanted to stay in sync with your question.
Public Function SecondOff(Schedule As Range) As Variant
Dim rw As Range, Temp As Variant
Dim i As Integer, j As Integer
Dim wrow As Integer, firstI As Integer, origI As Integer
ReDim Temp(1 To 6, 1 To 9)
For Each rw In Schedule.Range("2:" & Schedule.Rows.Count).Rows
i = 1
j = 2
wrow = rw.Cells(1, 1)
Temp(wrow, 1) = wrow
If (rw.Find("off", rw.Cells(1, i), xlValues, xlPart) Is Nothing) Then
Temp(wrow, 2) = "None"
Else
origI = rw.Find("off", rw.Cells(1, i), xlValues, xlPart).Column
Do
firstI = rw.Find("off", rw.Cells(1, i), xlValues, xlPart).Column
If firstI <= origI And j > 2 Then Exit Do
i = rw.Find("off", rw.Cells(1, firstI), xlValues, xlPart).Column
If i <= origI Then Exit Do
If i - firstI = 1 Then
Temp(wrow, j) = Schedule(1, i)
j = j + 1
Else
i = firstI
End If
Loop Until j > 8
End If
Next rw
SecondOff = Temp
End Function

Update a specific cell when a value changes in a column

I have been tasked with creating a way for a column of data to update the most recent entry to cell at the bottom. More specifically a loan portfolio amount is entered in for each month of this year and the most recent entry needs to also appear at the bottom of the column. Here's what I initally came up with but this won't work for the last entry before the bottom.
Private Sub Worksheet_Change(ByVal Target As Range)
xC = 0
yC = 7
If (Target.Column = 3) Then
Do
prevInt = currentInt
currentInt = Sheet1.Cells(yC, 3).Value
If (currentInt = 0) Then
Sheet1.Cells(19, 3).Value = prevInt
xC = 1
End If
yC = yC + 1
Loop Until xC = 1
End If
End Sub
i assume the cells below the most current month's total will always be blank, unless it's December. Otherwise i don't know how you'll know which is the most current:
Public Sub SetTotalCellValue()
Const portfolioColumn As Integer = 3
Const endRow As Integer = 14 'row of "total" cell
Dim sheet As Worksheet
Dim rowCount As Integer
Dim val As Object
rowCount = 2 'start at January, skip header row
'Set sheet = some sheet
'find last empty cell
For a = 1 To endRow
If sheet.Cells(a, portfolioColumn).Value = Empty Then
sheet.Cells(endRow, portfolioColumn).Value = sheet.Cells(a - 1, portfolioColumn).Value
Exit For
ElseIf a = endRow - 1 Then
sheet.Cells(endRow, portfolioColumn).Value = sheet.Cells(a, portfolioColumn).Value
Exit For
End If
Next a
End Sub

Resources