Skip to main content

paycheck_utils/
lib.rs

1//! This library contains utility functions for calculating paycheck withholdings and net income given a hypothetical hourly wage and weekly working hours. The idea is pretty much like the "Sample Paycheck" tool found in the [Paycom](https://www.paycom.com/software/employee-self-service/) employee portal, but aimed at having a little more functionality and customization.
2//!
3//! The entire library was developed with the perspective of an hourly paid employee in mind, focusing on bi-weekly paychecks as the standard pay period to simulate how employees typically view and plan their income.
4//!
5//! The primary question this library aims to answer is: "Given an hourly wage and number of hours worked per week, what would my net paycheck be after taxes and deductions?"
6//!
7//! The secondary question this library aims to answer is: "Given a total monthly expenses amount and hourly wage, how many hours would I need to work to cover my expenses with "x" amount left over after taxes and deductions?" (this will be implemented in a future version).
8//!
9//! The library is structured into several modules:
10//! - `withholdings`: Contains functions to estimate federal tax withholdings, Social Security, and Medicare deductions.
11//! - `deductions`: Defines structures and functions for handling pre-tax and post-tax deductions.
12//! - `income`: Contains functions to calculate gross paycheck based on hourly wage and hours worked.
13//! - `expenses`: Defines structures and functions for managing monthly expenses.
14//! - `utils`: Contains tax and time related constants necessary for calculations.
15//!
16//! A CLI will be implemented in a future version to allow a user to input their hourly wage, hours worked, filing status, deductions and expenses to see their estimated net paycheck and other relevant financial metrics.
17
18pub mod deductions;
19pub mod expenses;
20pub mod income;
21pub mod utils;
22pub mod withholdings;
23
24pub use crate::deductions::*;
25pub use crate::expenses::*;
26pub use crate::income::*;
27pub use crate::utils::*;
28pub use crate::withholdings::*;
29
30/// Represents an employment scenario with hourly rate, hours worked per week, filing status, and deductions.
31/// Possible deductions avaialable are defined in the `deductions` module.
32///
33/// # Example
34/// ```
35/// use paycheck_utils::FilingStatus;
36/// use paycheck_utils::EmploymentScenario;
37/// use paycheck_utils::PreTaxDeductions;
38/// use paycheck_utils::PreTaxDeduction;
39/// use paycheck_utils::PostTaxDeductions;
40/// use paycheck_utils::PostTaxDeduction;
41///
42/// let new_job_scenario = EmploymentScenario::new(
43///     30.0, // hourly rate
44///     40.0, // hours per week
45///     FilingStatus::Single, // filing status
46///     PreTaxDeductions::new(vec![
47///         PreTaxDeduction::Medical(Some(150.0)),
48///         PreTaxDeduction::Dental(Some(50.0)),
49///         PreTaxDeduction::Vision(Some(15.0)),
50///         PreTaxDeduction::Traditional401K(Some(200.0)),
51///     ]), // pre-tax deductions
52///     PostTaxDeductions::new(vec![PostTaxDeduction::Roth401K(Some(100.0))]) // post-tax deductions
53/// );
54/// ```
55pub struct EmploymentScenario {
56    pub hourly_rate: f32,
57    pub hours_per_week: f32,
58    pub filing_status: FilingStatus,
59    pub pretax_deductions: PreTaxDeductions,
60    pub posttax_deductions: PostTaxDeductions,
61}
62
63impl EmploymentScenario {
64    pub fn new(
65        hourly_rate: f32,
66        hours_per_week: f32,
67        filing_status: FilingStatus,
68        pretax_deductions: PreTaxDeductions,
69        posttax_deductions: PostTaxDeductions,
70    ) -> Self {
71        EmploymentScenario {
72            hourly_rate,
73            hours_per_week,
74            filing_status,
75            pretax_deductions,
76            posttax_deductions,
77        }
78    }
79
80    /// Calculates the net paycheck based on the employment scenario's parameters.
81    /// The calculations consider gross income, pre-tax deductions, federal tax withholdings, Social Security, Medicare, and post-tax deductions.
82    /// The IRS defined constants used to make calculations (such as tax rates, thresholds and standard deductions) are defined in the `utils` module.
83    /// This IRS method and flow for calculating withholdings is based on the 2026 federal tax year guidelines and can be summarized as follows:
84    ///    1. Calculate gross paycheck on hourly rate and hours worked.
85    ///    2. Subtract pre-tax deductions from gross paycheck to get adjusted gross paycheck.
86    ///    3. Calculate federal tax withholdings based on annualized adjusted gross paycheck and filing status.
87    ///    4. Calculate Social Security and Medicare withholdings based on adjusted gross paycheck.
88    ///    5. Subtract federal tax withholdings, Social Security, Medicare, and post-tax deductions from adjusted gross paycheck to get net paycheck.
89    ///
90    /// # Example
91    /// ```
92    /// use paycheck_utils::PostTaxDeduction;
93    /// use paycheck_utils::PreTaxDeduction;
94    /// use paycheck_utils::FilingStatus;
95    /// use paycheck_utils::EmploymentScenario;
96    /// use paycheck_utils::PreTaxDeductions;
97    /// use paycheck_utils::PostTaxDeductions;
98    ///
99    /// let pretax_deductions = PreTaxDeductions::new(vec![
100    ///     PreTaxDeduction::Medical(Some(100.0)),
101    ///     PreTaxDeduction::Dental(Some(50.0)),
102    ///     PreTaxDeduction::Vision(Some(25.0)),
103    ///     PreTaxDeduction::Traditional401K(Some(200.0)),
104    ///     PreTaxDeduction::HSA(Some(150.0)),
105    /// ]); // total = 525.0
106    /// let posttax_deductions = PostTaxDeductions::new(vec![
107    ///     PostTaxDeduction::Roth401K(Some(100.0)),
108    ///     PostTaxDeduction::VoluntaryLife(Some(30.0)),
109    /// ]); // total = 130.0
110    /// let scenario = EmploymentScenario::new(
111    ///     25.0, // hourly rate
112    ///     45.0, // hours per week (bi-weekly paycheck = 90 hours [10 hours overtime])
113    ///     FilingStatus::Single, // single filing status for standard deduction
114    ///     pretax_deductions, // total = 525.0
115    ///     posttax_deductions // total = 130.0
116    /// );
117    /// let net_paycheck = scenario.calculate_net_paycheck();
118    /// assert_eq!(net_paycheck, 1440.33);
119    ///
120    /// // Explanation of calculation:
121    /// // 1. Gross Paycheck: (25.0 * 80) + (25.0 * 10 * 1.5) = 2000.0 + 375.0 = 2375.0
122    /// // 2. Adjusted Gross Paycheck: 2375.0 - 525.0 = 1850.0  (after pre-tax deductions)
123    /// // 3. Federal Withholding (annualized AGP = 1850.0 * 26 = 48100.0): Using 2026 tax brackets for Single filer:
124    /// //    - 10% on first 12,400 = 12,400 * 0.10 = 1,240.0
125    /// //    - 12% on amount over 12,400 up to 50,400 = (48,100.0 - 12,400.0) * 0.12 = 4,290.0
126    /// //    - Total annual federal tax = 1,240.0 + 4,290.0 = 5,530.0
127    /// //    - Bi-weekly federal withholding = 5,530.0 / 26 = 212.69
128    /// // 4. Social Security Withholding: 1850.0 * 0.062 = 114.70
129    /// // 5. Medicare Withholding: 1850.0 * 0.0145 = 26.83
130    /// // 6. Post-Tax Deductions: 100.0 + 30.0 = 130.0
131    /// // 7. Total Deductions: 212.69 + 114.70 + 26.83 + 130.0 = 484.22
132    /// // 8. Net Paycheck: 1850.0 - 212.69 - 114.70 - 26.83 - 130.0 = 1440.33
133    /// ```
134    /// # Returns
135    /// An `f32` representing the calculated net paycheck amount.
136    ///
137    /// # Panics
138    /// This function does not explicitly panic, but it assumes that the input values (hourly rate, hours worked, deductions) are valid and reasonable.
139    ///
140    /// # Errors
141    /// This function does not return errors, but invalid input values may lead to incorrect calculations.
142    ///
143    /// # Notes
144    /// The calculations are based on the 2026 federal tax year guidelines and may need to be updated for future tax years.
145    pub fn calculate_net_paycheck(&self) -> f32 {
146        let mut gross_paycheck = determine_gross_paycheck(self.hourly_rate, self.hours_per_week);
147        let total_pretax = self.pretax_deductions.total_pretax_deductions();
148        gross_paycheck -= total_pretax;
149        let federal_withholding =
150            estimate_paycheck_federal_withholdings(gross_paycheck, self.filing_status);
151        let social_security = estimate_social_security_withholding(gross_paycheck);
152        let medicare = estimate_medicare_withholding(gross_paycheck);
153        let total_posttax = self.posttax_deductions.total_posttax_deductions();
154
155        round_2_decimals(
156            gross_paycheck - federal_withholding - social_security - medicare - total_posttax,
157        )
158    }
159}
160
161// UNIT TEST FOR LIBRARY
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166    #[test]
167    fn test_calculate_net_paycheck() {
168        let pretax_deductions = PreTaxDeductions::new(vec![
169            PreTaxDeduction::Medical(Some(100.0)),
170            PreTaxDeduction::Dental(Some(50.0)),
171            PreTaxDeduction::Vision(Some(25.0)),
172            PreTaxDeduction::Traditional401K(Some(200.0)),
173            PreTaxDeduction::HSA(Some(150.0)),
174        ]);
175        let posttax_deductions = PostTaxDeductions::new(vec![
176            PostTaxDeduction::Roth401K(Some(100.0)),
177            PostTaxDeduction::VoluntaryLife(Some(30.0)),
178        ]);
179        let _expenses = Expenses::new(vec![
180            Expense::Housing(Some(2000.0)),
181            Expense::Energy(Some(200.0)),
182            Expense::Water(Some(50.0)),
183            Expense::Internet(Some(60.0)),
184            Expense::Phone(Some(80.0)),
185            Expense::Vehicle(Some(300.0)),
186            Expense::VehicleInsurance(Some(150.0)),
187            Expense::VehicleGas(Some(100.0)),
188            Expense::Groceries(Some(400.0)),
189        ]);
190        let scenario = EmploymentScenario::new(
191            25.0,
192            45.0,
193            FilingStatus::Single,
194            pretax_deductions,
195            posttax_deductions,
196        );
197        let net_paycheck = scenario.calculate_net_paycheck();
198        assert_eq!(net_paycheck, 1440.33);
199    }
200}