Tag Archives: Rounding

My Work-Specific Vacation Time Function

Edit: There were some logic errors that Mark pointed out in the comments section. I was rounding throughout the function and skewing some of my own totals. I’ve updated this post to reflect all of the changes (code, images, text, etc.). Thank you for your time, Mark, for both reading the post and passing along some beneficial information.

I did that thing where I start a post that will include a shareable PowerShell function and I’ve yet to actually write any PowerShell. I’ll get there eventually, but for now, let me explain the purpose of the function I’ll be writing. Realistically, this explanation for you ought to help me with authoring the code. It’s obvious, but once I’ve defined the specifications and needs, it should be even easier to write.

My current employer — this is about to sound like a word problem from grade school — allows me to carry forward 320 vacation hours into the next calendar year. Anything above 320 hours is forfeited. Because I was on track to have greater than 320 hours by the end of the year, I’ve been taking off every Wednesday since the last week of September. If you haven’t been to Target at 8 a.m. on a weekday morning, then you’re missing out. It was so empty; it was just me and very few people — amazing. I digress.

My local HR is adamant about employees not losing/forfeiting vacation time. In all of this, I decided I should write the code to do the arithmetic in the PowerShell console. I later decided I should write a function that would do the calculations for me, instead. Before I actually construct the code and build the function, let’s walk through some needed values.

1. Number of carry forward vacation hours: 320 (default value, same for all employees)
2. Number of current vacation hours: 362.670
3. Number of pay periods during the remainder of the calendar year: 5
4. Hours of vacation accrued per pay period: 6.77 (default value, not the same for all employees)

First, I need to determine how many hours I currently have above the carry forward vacation hours of 320. That’s the current vacation hours minus the carry forward vacation hours: 362.670 – 320. It’s possible someone using my (so far non-existent) function could have less than 320 vacation hours. This would change things, and it’s probably worth keeping in mind, as the function will need to handle this possibility.

Second, I’d like to know how many more vacation hours I’ll accrue during the remainder of the calendar year. That’s the vacation accrual hours times the number of pay periods during the remainder of the calendar year: 6.77 * 5. Let’s go.

367.670 – 320 = 42.67
6.77 * 5 = 33.85
42.67 + 33.85 = 76.52

If we divide that total by 8 hours (as in, hours worked per day), we know how many days of vacation I need to take to use up my hours: 76.52 / 8 = 9.565. It’s a little over nine days, but not quite 10. In thinking through this assignment, I’ve realized that it’s possible someone may only want to know their current overage (the subtraction problem) versus their future overage (the multiplication problem) or vice versa. Anyway, I’m off to write this function now. To you, it’ll seem like no time passed at all; for me, I won’t be nearly as lucky. Still, having defined the function’s requirements upfront, before writing any code, has already simplify things.

Well, that’s done and working. I’m going to include images of the function because they’re easier on the eyes and I think it’ll simplify my code explanations. I will, however, make the function copyable at the end of this post.

Let’s start with the parameters.

This function includes five parameters. Beginning from the top, we have CarryForwardVacationHours. This has a default value of 320, however, this can be changed by the person/process when this function is invoked. It won’t need to be for me unless my current employer changes the carry forward amount. Next, we have CurrentVacationHours. The is the parameter that’s used to supply how many hours a person currently has. The value needs to be greater than the CarryForwardVacationHours or there won’t be any overage hours.

The following parameter is VacationAccrualRate. This contains a default value of 6.77 hours. This value does vary depending on how long a person has been employed by my current employer. This is the largest possible amount, as it’s based on longevity. The second to last parameter is PayPeriods. This is how many pay periods there are until the end of the calendar year. The person invoking the function will have to gather this information themself.

And finally, there is the Round parameter. This was a late addition to this function that was discussed in a recent post, Setting the Rounding Scale. This parameter has a default value of 2. This means that any values with decimals with more than 2 values, will be rounded down to two, or to whatever the value is set to when the function is invoked. As we saw in that recent post, it’s not going to add zeros into decimal positions. If the number is 42.1 and you indicate to round to three decimal places, it’s still going to be 42.1. Read the linked post to see how to add zeros if that’s something that would ever be of use to you.

Now, let’s move on to the Process block and its first region, Current Hours. It’s not pictured in these images, but the Begin block isn’t being used in this function. This is the reason it didn’t make it into this or the previous image.

This region of this Process block, first determines if the current vacation hours are greater than the carry forward vacation hours. If it is, it does a little math, and we’ll get there in a moment. If it isn’t, it sets the overage hours and overage days to zero. We’re storing all of our values in an ordered hash table, so that’s where these would land. If the current vacation hours are greater than the carry forward vacation hours, then it subtracts the two numbers as we mentioned earlier. In order to determine how many overage days there are, it divides the result by 8 hours per day, placing both values into the hash table. The carry forward hours are added to the hash table, as well.

Following this region, let’s discuss the Future/Total Hours region.

In this region, we determine our vacation accrual hours and from that total, our vacation accrual days. Finally, we put it all together for the total hours by combining the overage hours and vacation accrual hours and our total days by combing the overage days and the vacation accrual days. Much like the carry forward hours in the above region, we include the vacation accrual rate, too.

Let’s close out with the End Block. This is simple; we convert our hash table — the keys and values — into PowerShell objects, where they become properties and values. This way, they can be easier manipulated by other PowerShell commands if we choose to do that.

Now, let’s include a few test invocations and ensure we’re getting the proper results. This first one just calculates my vacation overage hours and days.

Show-VacationTime -CurrentVacationHours 362.670

CarryForward : 320
OverageHours : 42.67
OverageDays  : 5.33
AccrualRate  : 6.77
AccrualHours : 0
AccrualDays  : 0
TotalHours   : 42.67
TotalDays    : 5.33

This one returns my vacation overages, as well as my vacation accruals.

Show-VacationTime -CurrentVacationHours 362.670 -PayPeriods 5

CarryForward : 320
OverageHours : 42.67
OverageDays  : 5.33
AccrualRate  : 6.77
AccrualHours : 33.85
AccrualDays  : 4.23 
TotalHours   : 76.52
TotalDays    : 9.57 

These last example invocations run the same command as above but include the Round parameter with a couple of different values. Remember, 2 is the default value for this parameter.

Show-VacationTime -CurrentVacationHours 362.670 -PayPeriods 5 -Round 0

CarryForward : 320
OverageHours : 43
OverageDays  : 5
AccrualRate  : 6.77
AccrualHours : 34
AccrualDays  : 4
TotalHours   : 77
TotalDays    : 10

Show-VacationTime -CurrentVacationHours 362.670 -PayPeriods 5 -Round 4

CarryForward : 320
OverageHours : 42.67
OverageDays  : 5.3338
AccrualRate  : 6.77
AccrualHours : 33.85
AccrualDays  : 4.2312
TotalHours   : 76.52
TotalDays    : 9.565

Okay, one final example of when you don’t send in the current vacation hours to determine an overage, but you do want to know your vacation accrual hours.

Show-VacationTime -PayPeriods 5

CarryForward : 320
OverageHours : 0
OverageDays  : 0
AccrualRate  : 6.77
AccrualHours : 33.85
AccrualDays  : 4.23
TotalHours   : 33.85
TotalDays    : 4.23

Finally, as I promised, here’s a copyable version of this function. I’m quite aware that there are only so many of us that can and/or would make use of this function. Still, some great concepts have been demonstrated and maybe some of them can be included in some of your own projects.

Function Show-VacationTime {
    [CmdletBinding()]
    Param (
        [Parameter()]
        $CarryForwardVacationHours = 320,

        [Parameter()]
        $CurrentVacationHours,

        [Parameter()]
        $VacationAccrualRate = 6.77,

        [Parameter()]
        [int]$PayPeriods,

        [Parameter()]
        [ValidateRange(0,4)]
        [int]$Round = 2
    ) # End Param.

    Begin {} # End Begin.

    Process {
        #region: Current Hours.
        If ($CurrentVacationHours -gt $CarryForwardVacationHours) {
            $OverageHours = $CurrentVacationHours - $CarryForwardVacationHours
            $OverageDays = $OverageHours / 8 # 8 hours.
            $Hashtable = [ordered]@{
                CarryForward = $CarryForwardVacationHours
            }
        } Else {
            $Hashtable = [ordered]@{
                CarryForward = $CarryForwardVacationHours
                OverageHours = 0
                OverageDays = 0
            }
        } # End If-Else.
        #endregion.
        #region: Future/Total Hours.
        $VacationAccrualHours = $PayPeriods * $VacationAccrualRate
        $VacationAccrualDays = $VacationAccrualHours / 8
        $TotalHours = $OverageHours + $VacationAccrualHours
        $TotalDays = $OverageDays + $VacationAccrualDays
        $Hashtable['OverageHours'] = ([math]::Round($OverageHours,$Round))
        $Hashtable['OverageDays'] = ([math]::Round($OverageDays,$Round))
        $Hashtable['AccrualRate'] = $VacationAccrualRate
        $Hashtable['AccrualHours'] = ([math]::Round($VacationAccrualHours,$Round))
        $Hashtable['AccrualDays'] = ([math]::Round($VacationAccrualDays,$Round))
        $Hashtable['TotalHours'] = ([math]::Round($TotalHours,$Round))
        $Hashtable['TotalDays'] = ([math]::Round($TotalDays,$Round))
        #endregion.
    } # End Process.

    End {
        [PSCustomObject]$Hashtable
    } # End End.
} # End Function: Show-VacationTime.

Setting the Rounding Scale

Sometimes sharing one concept in a post, allows you to share another concept in another post. That brings us to today. This is that other post.

Originally, I had a great title for this post: It was Rounding Precision Decision. Then, for whatever reason, I went and made sure that precision was the word I was after. It wasn’t. Precision is the total number of digits in a number. Scale is the number of digits after the decimal.

Last night I decided I should add a Round parameter to a function I’m writing. This would allow the user to determine how rounding should be applied to a given number. The parameter values, as I see them now, will be 0 through 4. This is to say a numerical value may have zero decimal places up to four, depending on the person/process running the function. Neat, right? Like I sometimes do, I opened Window Terminal to start some one-off testing.

Let’s start with an ordered hash table of numeric values. As you’ll see, the values will vary in the number of decimal places from zero to five.

[PS7.1.5][C:\] $Hash = [ordered]@{'Num0' = 42; 'Num1' = 42.1; 'Num2' = 42.12; 'Num3' = 42.123; 'Num4' = 42.1234; 'Num5' = 42.12345}
[PS7.1.5][C:\] $Hash

Name               Value
----               -----
Num0               42
Num1               42.1
Num2               42.12
Num3               42.123
Num4               42.1234
Num5               42.12345
[PS7.1.5][C:\] 

While we’re here, let’s loop through the keys and values in this hash table. We’ll need this construct soon enough anyway.

[PS7.1.5][C:\] Foreach ($Key in $Hash.Keys) {"$Key`: $($Hash[$Key])"}
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.1234
Num5: 42.12345
[PS7.1.5][C:\] 

That works. Now what we need to consider is that we’re going to need to iterate over $Hash multiple times, so we can apply each of the decimal values, 0 – 5 for this post. This may be confusing, but the next example should help explain how we get there.

[PS7.1.5][C:\] Foreach ($Value in 0..5) {$Value}
0
1
2
3
4
5
[PS7.1.5][C:\] Foreach ($Value in 0..5) {Foreach ($Key in $Hash.Keys) {"$Key`: $($Hash[$Key])"}}
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.1234
Num5: 42.12345
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.1234
Num5: 42.12345
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.1234
Num5: 42.12345
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.1234
Num5: 42.12345
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.1234
Num5: 42.12345
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.1234
Num5: 42.12345
[PS7.1.5][C:\]

Now that we have this working, let’s apply some rounding to each of the sets of numbers. Before we do that, however, I’m heading back to VSCode. Sometimes the one-off code I write gets a bit unwieldy. For reference, we’re going to use the Round method in the System.Math class, such as [math]::Round(<number>,<scale>).

$Hash = [ordered]@{
	'Num0' = 42
	'Num1' = 42.1
	'Num2' = 42.12
	'Num3' = 42.123
	'Num4' = 42.1234
	'Num5' = 42.12345
}
Foreach ($Value in 0..5) {
	"Rounding to $Value"
	Foreach ($Key in $Hash.Keys) {
		"$Key`: $([math]::Round($Hash[$Key],$Value))"
	} # End Foreach.
'--separator--'
} # End Foreach.

Rounding to 0
Num0: 42
Num1: 42
Num2: 42
Num3: 42
Num4: 42
Num5: 42
--separator--
Rounding to 1
Num0: 42
Num1: 42.1
Num2: 42.1
Num3: 42.1
Num4: 42.1
Num5: 42.1
--separator--
Rounding to 2
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.12
Num4: 42.12
Num5: 42.12
--separator--
Rounding to 3
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.123
Num5: 42.123
--separator--
Rounding to 4
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.1234
Num5: 42.1234
--separator--
Rounding to 5
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.1234
Num5: 42.12345
--separator--

At first glance, this PowerShell code may look like it’s failing. Look at the “Rounding to 3” section, for instance. There are values where it appeared to round to zero, one, and two decimal places. Remember though, these numbers didn’t start with three decimals places or more. It did exactly what we asked it to do to the best of its ability. I don’t know of a way that the Round method can add zeros if we actually wanted that — let me know if you — but there’s another way!

This time, we’ll use the -f format operator and force zeros into the decimals places. I probably won’t do this in my upcoming post, but I think it’s worth seeing, as there’s value here.

Foreach ($Value in 0..5) {
	"Rounding to $Value"
	Foreach ($Key in $Hash.Keys) {
		"$Key`: $("{0:N$Value}" -f $Hash[$Key])"
	} # End Foreach.
'--separator--'
} # End Foreach.

Rounding to 0
Num0: 42
Num1: 42
Num2: 42
Num3: 42
Num4: 42
Num5: 42
--separator--
Rounding to 1
Num0: 42.0
Num1: 42.1
Num2: 42.1
Num3: 42.1
Num4: 42.1
Num5: 42.1
--separator--
Rounding to 2
Num0: 42.00
Num1: 42.10
Num2: 42.12
Num3: 42.12
Num4: 42.12
Num5: 42.12
--separator--
Rounding to 3
Num0: 42.000
Num1: 42.100
Num2: 42.120
Num3: 42.123
Num4: 42.123
Num5: 42.123
--separator--
Rounding to 4
Num0: 42.0000
Num1: 42.1000
Num2: 42.1200
Num3: 42.1230
Num4: 42.1234
Num5: 42.1234
--separator--
Rounding to 5
Num0: 42.00000
Num1: 42.10000
Num2: 42.12000
Num3: 42.12300
Num4: 42.12340
Num5: 42.12345
--separator--

And that’s, that. Back to the other post that inspired this one. See you there in time!