Extract two numbers from string using formula - vba

I have formula to extract numbers from Google XML response, that can be:
9 часов 24 минут
10 h 0 min
9 h 20 min
11 h 13 min
10 Std 55 min
9 timmar 5 min
19 min
Here is current formula (value is in BZ1):
=IFERROR(IF(VALUE(IF(FIND("min";BZ1;1)=11;MID(BZ1;FIND("min";BZ1;1)-2;1);MID(BZ1;FIND("min";BZ1;1)-3;2)))<30; VALUE(LEFT(BZ1;FIND("h";BZ1;1)-2))&","&5;VALUE(LEFT(BZ1;FIND("h";BZ1;1)-2))+1);"")
Formula rounds hours and minutes to hours, for example
1 h 39 min -> 2
10 h 12 min -> 10,5
9 h 20 min -> 9,5
There is a problem that it is not able to take in consideration language changes for hours and min.
Is there any possibility to make it work so that it will:
If there is only number (case 19 min) -> extract number
If there are two numbers (case 1 h 39 min) -> extract first number as hours, then from last two spaces number as minutes
EDIT:
Check how many numbers in cell (target in CA25):
SUM(LEN(CA25)-LEN(SUBSTITUTE(CA25;{1;2;3;4;5;6;7;8;9};)))>1
If more than 1
LEFT(CA25;(FIND(" ";CA25;1)-1))&TRIM(MID(SUBSTITUTE(CA25;" ";REPT(" ";50));100;50))
If less than 1
LEFT(CA25;SUM(LEN(CA25)-LEN(SUBSTITUTE(CA25;{"0";"1";"2";"3";"4";"5";"6";"7";"8";"9"};""))))
All together
=IF(SUM(LEN(CA25)-LEN(SUBSTITUTE(CA25;{1;2;3;4;5;6;7;8;9};)))>1;LEFT(CA25;(FIND(" ";CA25;1)-1))&TRIM(MID(SUBSTITUTE(CA25;" ";REPT(" ";50));100;50));LEFT(CA25;SUM(LEN(CA25)-LEN(SUBSTITUTE(CA25;{"0";"1";"2";"3";"4";"5";"6";"7";"8";"9"};"")))))
This gives as output:
Now these need to be converted to hours and rounded up to one hour
EDIT 2:
Here is formula (target BZ1):
=IFERROR(IF(LEN(BZ1)-LEN(SUBSTITUTE(BZ1;" ";""))>2;LEFT(BZ1;(FIND(" ";BZ1;1)-1))+IF(TRIM(MID(SUBSTITUTE(BZ1;" ";REPT(" ";50));100;50))<60;1;1);IF(LEFT(BZ1;SUM(LEN(BZ1)-LEN(SUBSTITUTE(BZ1;{"0";"1";"2";"3";"4";"5";"6";"7";"8";"9"};""))))<60;1;1));"")

Here is a small user defined function:
Option Explicit
Public Function Tyme(inpt As String) As Double
Dim arr, U As Long
Tyme = 0
arr = Split(inpt, " ")
U = UBound(arr)
If U = 0 Then Exit Function
If U = 1 Then
Tyme = CDbl(arr(0))
Else
Tyme = CDbl(arr(0)) + CDbl(arr(2)) / 60#
End If
End Function
It:
is language-independent
returns an un-rounded floating point value (hours)
Some examples:
User Defined Functions (UDFs) are very easy to install and use:
ALT-F11 brings up the VBE window
ALT-I
ALT-M opens a fresh module
paste the stuff in and close the VBE window
If you save the workbook, the UDF will be saved with it.
If you are using a version of Excel later then 2003, you must save
the file as .xlsm rather than .xlsx
To remove the UDF:
bring up the VBE window as above
clear the code out
close the VBE window
To use the UDF from Excel:
=Tyme(A1)
To learn more about macros in general, see:
http://www.mvps.org/dmcritchie/excel/getstarted.htm
and
http://msdn.microsoft.com/en-us/library/ee814735(v=office.14).aspx
and for specifics on UDFs, see:
http://www.cpearson.com/excel/WritingFunctionsInVBA.aspx
Macros must be enabled for this to work!
NOTES:
rounding should be applied outside the udf
it would be easy to modify the udf to handle seconds as well
it would be easy to modify the udf to return true Excel time rather than floating point hours

May I suggest you use a VBA function? Seeing as in your examples there is always a space between the numbers and other characters, this should do it:
Public Function TimeInHoursRoundedUp(rng as Range)
Dim var As Variant: var = Split(rng, " ")
Dim item As Variant
Dim hour As Integer: hour = 0
For Each item In var
If IsNumeric(item) Then
If hour = 0 Then hour = hour + item Else hour = hour + 1
End If
Next item
TimeInHoursRoundedUp = hour
End Sub
Then in your excel sheet you could simply write =TimeInHoursRoundUp() and input the cell reference inside the brackets.

Related

Only Show Full X-Axis Intervals VB.NET

I'm using the .NET charting library to show a line graph (voltage versus time). The full data length always varies (can be between 10ms of data up to ~250ms of data).
The issue I'm running into is that the final interval on the chart is usually cut short. For example if there is 230ms of data, and the chart has 50ms intervals, the final interval will be cut off after 30ms and won't show the complete interval on the x-axis. Ideally I want it to go all the way to 250ms and simply stop showing the data after 230ms (but extend the x-axis range to fully complete the interval).
Is there an option somewhere to not cut intervals short if the data ends? I haven't been able to find anything.
Just set the min and max explicitly
' make a test series, add points
Dim s As New Series()
For i = 0 To 230
s.Points.Add(New DataPoint(i, i))
Next
' add series to chart
Chart1.Series.Add(s)
' set axis interval, min, and max
Chart1.ChartAreas(0).AxisX.Interval = 50
Chart1.ChartAreas(0).AxisX.Minimum = 0
Chart1.ChartAreas(0).AxisX.Maximum = 250
If you don't want to hard-code it, it can be done programatically
Chart1.ChartAreas(0).AxisX.Maximum = Math.Ceiling(maxValue / interval) * interval
If you don't want to set the interval, you can calculate it based on the max value. This is an example but you may want to tweak the ranges and values
Private Function getInterval(maxValue As Double) As Double
Select Case maxValue
Case 0 To 10
Return 1
Case 10 To 50
Return 5
Case 50 To 100
Return 10
Case 100 To 1000
Return 50
Case Else
Return 100
End Select
End Function
'''
Dim maxValue = 33
' make a test series, add points
Dim s As New Series()
For i = 1 To maxValue
s.Points.Add(New DataPoint(i, i))
Next
' add series to chart
Chart1.Series.Add(s)
Dim interval = getInterval(maxValue)
Chart1.ChartAreas(0).AxisX.Minimum = 0
Chart1.ChartAreas(0).AxisX.Maximum = Math.Ceiling(maxValue / interval) * interval

Specifying R1C1 in Excel using Variables in a For...Next Loop

I'm trying to run a For Next loop that pulls specific numbers from specific cells in order to calculate total hours for a time card using Excel (it's the best program for what I'm using it for). I cannot, however figure out how to do it because I'm using R1C1 because I'm running the a count for the locations. I am not hidebound to this particular method of doing things, but a variation on it worked in Access (but I can't use Access for this because, reasons).
Attached is the code snippet I'm using.
For Weekday = 11 To 17 'Determine the day of the week by Row Reference (R1C1 format)
For DayValue = 29 To 34 'Determin time input (Call, Meal In, Meal Out, Wrap, etc.) for day of the week
WrkTimes(DayValue) = ActiveSheet.Cells.FormulaR1C1 = "R" & (Weekday) & "C" & (DayValue) 'Select the correct time input
Next DayValue 'Process the next time input
TotHrs(Weekday) = WrkTimes(34) - WrkTimes(29) - (WrkTimes(31) - WrkTimes(30)) - (WrkTimes(33) - WrkTimes(32)) 'Calculate total hours for the day
ActiveSheet.Cells(Weekday, 37) = TotHrs(Weekday) 'Display total hours for the day in the proper cell
Yes there is a Next command at the bottom of this loop, I just have other non-relevant code between this and the end. I'm getting the error on the DayValue lines
My first suggestion is to use
Cells(r, c)
Then you can put in your offsets such as
Cells(r-3, c)
You would just need to store the r and c values, which you've done as Weekday (r) and DayValue (c).
In the case if what you're looking at with the loop:
Cells(Weekday, DayValue).Value
This would give the value in the cell you've referenced.
Edit:
Suggestion from my comment:
Dim i as Long, j as Long, k as Long
For Weekday = 11 To 17
i = Cells(Weekday, 34).Value 'Should allow you to have an output for each row
For DayValue = 33 To 29 Step -1 'Saving column 34 outside of the dayvalue loop
j = i
k = Cells(Weekday, DayValue).Value
i = j - k
Next DayValue
Cells(Weekday, 37).Value = i
Next Weekday
So, to clarify why I kept getting the subscript out of range (after following the suggestions from Scott and Phylogenesis), it turns out that my Weekday(x) was set for 7 when it should have been set at 17.
Thanks for all the help.

VBA: Return 2nd smallest number from list

Using: Excel 2010, VBA
Goal: Return second smallest number (whether it's an integer or real number with decimals) from a list
Example Dataset (I'd want it to return 10.123):
11
9
26.0
37.123
45
10.123
Problem: I came across several threads that explain how to do this, but it seems like everything only pertains to integers. I need this to work for a list of values that are integers and real numbers (e.g., 1 and 1.2345).
What I've Tried (but I think this only works for integers):
For dblX = LBound(arrFwdTimes) To UBound(arrFwdTimes) - 1
For dblY = dblX + 1 To UBound(arrFwdTimes)
If arrFwdTimes(dblX) > arrFwdTimes(dblY) Then
strT = arrFwdTimes(dblY)
arrFwdTimes(dblY) = arrFwdTimes(dblX)
arrFwdTimes(dblX) = strT
End If
Next dblY
Next dblX
You can use the following to achieve what you want. This is the same as using the SMALL formula function
ans = WorksheetFunction.Small(YourRange,2)

User-defined function calculating production growth with variable start dates and growth profile

Struggling with an excel user defined function to calculate a total production generated by different bacteria patches starting at different times but following the same growth pattern; I have tried to simplify my problem below:
Example tables
PROFILE table: I have a 2 columns x 6 rows Table (A2:B9) showing a bacteria production per week depending on the age of the colony (column A gives the period, column B the production: in the first 3 weeks the bacteria produce 1/week in the next 3 weeks they produce 2 per week etc); I call this table my production PROFILE, this may vary depending on the type of bacteria I have (and other environmental parameters). I have decided in this particular example, and to keep it simple to show the population growth per period in a table, the growth values could of course be generated by a function (linear, exponential, with a decay factor etc) I guess that if I can crack the problem, adding a level of complexity with a growth function shouldn't be an issue.
RESULT table: I then have a 3 columns 20 rows table (A14:C33) which shows over a 20 week period (my 20 rows, numbered 1 to 20 in column A) when I start some bacteria cultures (1 in week 3, 2 in week 6 etc), I call it my RESULT table
I'd like to show in column C of the RESULT the total production of the colonies for each week.
I tried creating a PROD(week, PROFILE) function where I defined both "week" and "PROFILE" as variants and where "PROFILE" actually relates to my PROFILE table. It works fine when "week" is an individual cells (ie PROD(A18,PROFILE)=2) but doesn't work with ranges (PROD(A14:A33,PROFILE) returns an error message)
Function PROD(period As Variant, profile As Variant) As Variant
r = profile.Rows.Count
If profile(1, 1) >= period Then
PROD = profile(1, 2)
Else
For i = 2 To r
If profile(i, 1) >= period Then
If profile(i - 1, 1) < period Then
PROD = profile(i, 2)
End If
End If
Next i
End If
PROD = Application.Round(PROD, 2)
End Function
is there an elegant solution to populate column C of RESULT?
I did a similar thing on a previous assignment (finance) combining a sumproduct with the excel pmt function ( -pmt(rate, nper, pv,..) where pv was a range) and this did work, I managed to get a nice calculation of my total depreciation cost on a given period when I could have had items purchased in different quantities and at variable prices over the previous periods. the formula I used back then, shown on the attached Example of DepTable&Formula is
SUMPRODUCT(-PMT($C$7,$C6,$C$3:$V$3),N($C$4:$V$4<=C$2),N(($C$4:$V$4+$C6)>C$2))
I tried to replicate it here with a custom function with my bacteria population but I am really stuck.
I can't quite imagine what your profile table might look like nor, in fact, what exactly you want to extract. Perhaps my attempt below gives you some ideas. Please study it and let me know where it needs improvement.
Function PROD(period As Variant, profile As Variant) As Variant
Dim Fun As Variant
Dim R As Long
R = 1 ' should this always be 1?
Do
Fun = Fun + profile(R, 2).Value
R = R + 1
If R > profile.Rows.Count Then Exit Do
Loop While profile(R, 1) >= period
PROD = Application.Round(Fun, 2)
End Function
I wouldn't use this as a UDF, however. Instead, I would modify it slightly to write directly into the C column. Let the function be called from the Worksheet_Change event, linked to the profile table, or perhaps both tables, so that the data update automatically whenever there is a change. With the UDF updating will take place only when the sheet is recalculated, and I found that not to be easily controlled.
If this isn't what you want then it should be a lot nearer to it than what I did this morning. Take a look.
Function PROD(Period As Range) As Single
Dim Fun As Single
Dim Periods As Integer
Dim Weeks As Integer
Dim A As Integer
Dim R As Long
Periods = Period.Cells.Count
For A = 1 To Periods
Weeks = CInt(Period.Cells(A).Value)
With Range("Profile")
For R = 1 To (.Rows.Count - 1)
If .Cells(R, 1).Value >= Weeks Then Exit For
Next R
Fun = Fun + .Cells(R, 2).Value
End With
Next A
PROD = Application.Round(Fun / Periods, 2)
End Function
Call this function with =Prod(A14:A20) or =Prod(A14:A14) or = Prod(A14)
It will extract one result from the Profile table for each of the specified weeks. A14:A20 will have 7 results, and so would A16:A22. The function draws an average. So, with 7 results, these seven are added up and divided by 7. Perhaps this isn't yet what you want, but you might be able to manipulate the result of the function to meet your requirements.

How to convert Quarter years to other format

I have cells that can either contain time in this format:
1625 (16 for 2016 and 25 for week 25)
Or in this format
2016-Q2 (Q2 means quarter 2 if the year)
When converting I want quarters to be the mid week of the quarter
2016-Q1 = 1608
2016-Q2 = 1620
2016-Q3 = 1633
2016-Q4 = 1646
I dont want to convert the times in the cell its in. I want to convert it to YYWW format for a formula for a timeline in another sheet. So I use help cells with the converted value and reference those instead of the values in the other sheet.
I have done this with nested if functions resulting in mile long formulas because the timeline needs to be very long and the time can very well be 2025-Q3.
a =IF('Gulpilspuls NT'!U4="2016-Q1";1608;IF('Gulpilspuls NT'!U4="2016-Q2";1620;IF('Gulpilspuls NT'!U4="2016-Q3";1633;IF('Gulpilspuls NT'!U4="2016-Q4";1646;IF('Gulpilspuls NT'!U4="2017-Q1";1708;IF('Gulpilspuls NT'!U4="2017-Q2";1720;IF('Gulpilspuls NT'!U4="2017-Q3";1733;IF('Gulpilspuls NT'!U4="2017-Q4";1746;IF('Gulpilspuls NT'!U4="2018-Q1";1808;IF('Gulpilspuls NT'!U4="2018-Q2";1820;IF('Gulpilspuls NT'!U4="2018-Q3";1833;IF('Gulpilspuls NT'!U4="2018-Q4";1846;IF('Gulpilspuls NT'!U4="2019-Q1";1908;IF('Gulpilspuls NT'!U4="2019-Q2";1920;IF('Gulpilspuls NT'!U4="2019-Q3";1933;IF('Gulpilspuls NT'!U4="2019-Q4";1946;IF('Gulpilspuls NT'!U4="2020-Q1";2008;IF('Gulpilspuls NT'!U4="2020-Q2";2020;IF('Gulpilspuls NT'!U4="2020-Q3";2033;IF('Gulpilspuls NT'!U4="2020-Q4";2046;IF('Gulpilspuls NT'!U4="2021-Q1";2108;IF('Gulpilspuls NT'!U4="2021-Q2";2120;IF('Gulpilspuls NT'!U4="2021-Q3";2133;IF('Gulpilspuls NT'!U4="2021-Q4";2146;IF('Gulpilspuls NT'!U4="2022-Q1";2208;IF('Gulpilspuls NT'!U4="2022-Q2";2220;IF('Gulpilspuls NT'!U4="2022-Q3";2233;IF('Gulpilspuls NT'!U4="2022-Q4";2246;IF('Gulpilspuls NT'!U4="2023-Q1";2308;IF('Gulpilspuls NT'!U4="2023-Q2";2320;IF('Gulpilspuls NT'!U4="2023-Q3";2333;IF('Gulpilspuls NT'!U4="2023-Q4";2346;IF('Gulpilspuls NT'!U4="2024-Q1";2408;IF('Gulpilspuls NT'!U4="2024-Q2";2420;IF('Gulpilspuls NT'!U4="2024-Q3";2433;IF('Gulpilspuls NT'!U4="2024-Q4";2446;IF('Gulpilspuls NT'!U4="2025-Q1";2508;IF('Gulpilspuls NT'!U4="2025-Q2";2520;IF('Gulpilspuls NT'!U4="2025-Q3";2533;IF('Gulpilspuls NT'!U4="2025-Q4";2546;IF('Gulpilspuls NT'!U4="2026-Q1";2608;IF('Gulpilspuls NT'!U4="2026-Q2";2620;IF('Gulpilspuls NT'!U4="2026-Q3";2633;IF('Gulpilspuls NT'!U4="2026-Q4";2646;IF('Gulpilspuls NT'!U4="2027-Q1";2708;IF('Gulpilspuls NT'!U4="2027-Q2";2720;IF('Gulpilspuls NT'!U4="2027-Q3";2733;IF('Gulpilspuls NT'!U4="2027-Q4";2746;IF('Gulpilspuls NT'!U4="2028-Q1";2808;IF('Gulpilspuls NT'!U4="2028-Q2";2820;IF('Gulpilspuls NT'!U4="2028-Q3";2833;IF('Gulpilspuls NT'!U4="2028-Q4";2846;IF('Gulpilspuls NT'!U4="2029-Q1";2908;IF('Gulpilspuls NT'!U4="2029-Q2";2920;IF('Gulpilspuls NT'!U4="2029-Q3";2933;IF('Gulpilspuls NT'!U4="2029-Q4";2946;IF('Gulpilspuls NT'!U4="2030-Q1";3008;IF('Gulpilspuls NT'!U4="2030-Q2";3020;IF('Gulpilspuls NT'!U4="2030-Q3";3033;IF('Gulpilspuls NT'!U4="2030-Q4";3046;IF('Gulpilspuls NT'!U4="2031-Q1";3108;IF('Gulpilspuls NT'!U4="2031-Q2";3120;IF('Gulpilspuls NT'!U4="2031-Q3";3146;IF('Gulpilspuls NT'!U4="2031-Q4";3146;IF('Gulpilspuls NT'!U4="2032-Q1";3208;IF('Gulpilspuls NT'!U4="2032-Q2";3220;IF('Gulpilspuls NT'!U4="2032-Q3";3233;IF('Gulpilspuls NT'!U4="2032-Q4";3246;IF('Gulpilspuls NT'!U4="2033-Q1";3308;IF('Gulpilspuls NT'!U4="2033-Q2";3320;IF('Gulpilspuls NT'!U4="2033-Q3";3333;IF('Gulpilspuls NT'!U4="2033-Q4";3346;IF('Gulpilspuls NT'!U4="2034-Q1";3408;IF('Gulpilspuls NT'!U4="2034-Q2";3420;IF('Gulpilspuls NT'!U4="2034-Q3";3433;IF('Gulpilspuls NT'!U4="2034-Q4";3446;IF('Gulpilspuls NT'!U4="2035-Q1";3508;IF('Gulpilspuls NT'!U4="2035-Q2";3520;IF('Gulpilspuls NT'!U4="2035-Q3";3533;IF('Gulpilspuls NT'!U4="2035-Q4";3546;IF('Gulpilspuls NT'!U4="2036-Q1";3608;IF('Gulpilspuls NT'!U4="2036-Q2";3620;IF('Gulpilspuls NT'!U4="2036-Q3";3633;IF('Gulpilspuls NT'!U4="2036-Q4";3646;IF('Gulpilspuls NT'!U4="2037-Q1";3708;IF('Gulpilspuls NT'!U4="2037-Q2";3720;IF('Gulpilspuls NT'!U4="2037-Q3";3733;IF('Gulpilspuls NT'!U4="2037-Q4";3746;IF('Gulpilspuls NT'!U4="2038-Q1";3808;IF('Gulpilspuls NT'!U4="2038-Q2";3820;IF('Gulpilspuls NT'!U4="2038-Q3";3833;IF('Gulpilspuls NT'!U4="2038-Q4";3846;'Gulpilspuls NT'!U4))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
As you can clearly see this method is not the best. I cant make it this long because nested if functions can only contain 64 levels of nesting. Do you guys have a better suggestion for this?
The formula has to work with both formats of time entry and I need it to be able to convert all the cells 1:1 meaning 1 cell in the calendar has to be 1 converted cell in the other spot that I can use for the formula. If the cell in the calendar does not have YYYY-Q1234? it should just show what it is instead as you can see at the end of my formula.
Here is your formula.
=IF(AND(LEN(C6)=4,ISERROR(FIND("-",C6))),C6,MID(C6,3,2)&INDEX({"08",20,33,46},RIGHT(C6,1)))
Make sure there is no excess space in your data. Not like this "2016-Q1 ", but like this "2016-Q1".
EDIT: I just realized that if the original is in the format of YYWW, that you do NOT want it changed to the mid quarter week number. So we simplify the formulas:
=IF(ISNUMBER(-A1),A1,MID(A1,3,2) & CHOOSE(RIGHT(A1,1),"08",20,33,46))
and if you want YYWW to always be rendered as numeric:
=1*IF(ISNUMBER(-A1),A1,MID(A1,3,2) & CHOOSE(RIGHT(A1,1),"08",20,33,46))
And here are the results for various samples:
EDIT: If you need to check for blanks, you can do this simply:
=IF(LEN(A1)=0,"",1*IF(ISNUMBER(-A1),A1,MID(A1,3,2) & CHOOSE(RIGHT(A1,1),"08",20,33,46)))
However, if a 0 will not result in a downstream problem, you can use the original, shorter formula, and merely use a custom format to suppress zero returns: 0;;
And if you need to check for other conditions for which you don't want to process, you can perform similar actions.
something like this should do it, however, mid way through Q1, is week 6, so you'll need to adjust if your year doesn't start at 1/1
Function get_week(strInput As String) As String
Dim strQ As String
Dim bytQ As Byte
Dim dblMultiplier As Double
Dim intWeekNumber As Integer
strQ = Split(strInput, "-")(0)
bytQ = CByte(Right(strQ, 1))
dblMultiplier = (bytQ - 1) / 4
intWeekNumber = (dblMultiplier * 52)
intWeekNumber = intWeekNumber + (13 / 2)
get_week = Split(strInput, "-")(1) & "-" & CStr(intWeekNumber)
End Function
Let me try again
="20"&LEFT(N5,2)&IF(MOD(N5,100)<=8,"-Q1",IF(MOD(N5,100)<=20,"-Q2",IF(MOD(N5,100)<=33,"-Q3",if(MOD(N5,100)<=46,"-Q4","-Q1"))))
Should work for everything past year 2000 ;)