Repository: ImperialCollegeLondon/PyProBE

Issue:
Add maccor preprocessor

Discussion:

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e7f35e8..87384e5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,12 +1,12 @@
 name: "Build and test"
 on:
   pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
     branches:
       - main
   workflow_dispatch:
 jobs:
   qa:
-    if: github.event.pull_request.draft == false
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
diff --git a/docs/source/user_guide/input_data_guidance.rst b/docs/source/user_guide/input_data_guidance.rst
index ae14e1e..bc79dd8 100644
--- a/docs/source/user_guide/input_data_guidance.rst
+++ b/docs/source/user_guide/input_data_guidance.rst
@@ -16,6 +16,19 @@ PyProBE is able to import data from the following cyclers:
   - .mpt
   - .txt
 
+* Arbin: :code:`'arbin'`
+  
+  - .csv
+  - .xlsx
+
+* Maccor: :code:`'maccor'`
+
+  - .csv
+
+* Basytec: :code:`'basytec'`
+
+  - .txt
+
 
 PyProBE data columns
 --------------------
@@ -42,34 +55,83 @@ Once converted into the standard PyProBE format, the data columns stored in
    current is passed and decreases when discharge current is passed.
 
 The table below summarises the data columns in the PyProBE format and the corresponding
-column names of of data from supported cyclers:
+column names that are required in data from supported cyclers:
+
+.. raw:: html
+
+   <style>
+      .scrollable-table-container {
+           position: relative;
+           overflow-x: auto;
+           white-space: nowrap;
+      }
+      .scrollable-table {
+           border-collapse: collapse;
+           width: 100%;
+      }
+      .scrollable-table th, .scrollable-table td {
+           padding: 8px;
+           text-align: left;
+           border: 1px solid #ddd;
+      }
+      .scrollable-table th {
+           background-color: #f2f2f2;
+           position: sticky;
+           top: 0;
+           z-index: 1;
+      }
+      .scrollable-table th:first-child, .scrollable-table td:first-child {
+           position: sticky;
+           left: 0;
+           z-index: 2;
+           background-color: #f2f2f2;
+      }
+      .scrollable-table th:nth-child(2), .scrollable-table td:nth-child(2) {
+           position: sticky;
+           left: 115px; /* Adjust this value based on the width of the first column */
+           z-index: 2;
+           background-color: #f2f2f2;
+      }
+   </style>
+
+   <div class="scrollable-table-container">
 
 .. table::
-   :widths: 20 20 20 20
-
-   +----------------+-----------+------------------------+-----------------------------+
-   | PyProBE        | Required? | Neware                 | BioLogic                    |
-   +================+===========+========================+=============================+
-   | Date           | No        | Date                   | "Acquisition started on"    |
-   |                |           |                        | in header                   |
-   +----------------+-----------+------------------------+-----------------------------+
-   | Time [s]       | Yes       | *Auto from Date*       | time/*                      |
-   +----------------+-----------+------------------------+-----------------------------+
-   | Step           | Yes       | Step Index             | Ns                          |
-   +----------------+-----------+------------------------+-----------------------------+
-   | Cycle          | Yes       | *Auto from Step*       | *Auto from Step*            |
-   |                |           |                        |                             |
-   +----------------+-----------+------------------------+-----------------------------+
-   | Event          | Yes       | *Auto from Step*       | *Auto from Step*            |
-   |                |           |                        |                             |
-   +----------------+-----------+------------------------+-----------------------------+
-   | Current [A]    | Yes       | Current(*)             | I/*                         |
-   +----------------+-----------+------------------------+-----------------------------+
-   | Voltage [V]    | Yes       | Voltage(*)             | Ecell/*                     |
-   +----------------+-----------+------------------------+-----------------------------+
-   | Capacity [Ah]  | Yes       | Chg. Cap.(*),          | Q charge/\*,                |
-   |                |           | DChg. Cap.(*)          | Q discharge/*               |
-   +----------------+-----------+------------------------+-----------------------------+
+   :widths: 20 20 20 20 20 20 20
+   :class: scrollable-table
+   
+   +----------------------+-----------+------------------------+-----------------------------+-----------------------------+-----------------------------+-----------------------------+
+   | PyProBE              | Required? | Neware                 | BioLogic                    | Arbin                       | Maccor                      | Basytec                     |
+   +======================+===========+========================+=============================+=============================+=============================+=============================+
+   | ``Date``             | No        | ``Date``               | ``Acquisition started on``  | ``Date Time``               | ``DPT Time``                | ``~Start of Test``          |
+   |                      |           |                        | in header                   |                             |                             | in header                   |
+   +----------------------+-----------+------------------------+-----------------------------+-----------------------------+-----------------------------+-----------------------------+
+   | ``Time [s]``         | Yes       | *Auto from Date*       | ``time/*``                  | ``Test Time (*)``           | ``Test Time (sec)``         | ``~Time[*]``                |
+   +----------------------+-----------+------------------------+-----------------------------+-----------------------------+-----------------------------+-----------------------------+
+   | ``Step``             | Yes       | ``Step Index``         | ``Ns``                      | ``Step Index``              | ``Step``                    | ``Line``                    |
+   +----------------------+-----------+------------------------+-----------------------------+-----------------------------+-----------------------------+-----------------------------+
+   | ``Cycle``            | Yes       | *Auto from Step*       | *Auto from Step*            | *Auto from Step*            | *Auto from Step*            | *Auto from Step*            |
+   |                      |           |                        |                             |                             |                             |                             |
+   +----------------------+-----------+------------------------+-----------------------------+-----------------------------+-----------------------------+-----------------------------+
+   | ``Event``            | Yes       | *Auto from Step*       | *Auto from Step*            | *Auto from Step*            | *Auto from Step*            | *Auto from Step*            |
+   |                      |           |                        |                             |                             |                             |                             |
+   +----------------------+-----------+------------------------+-----------------------------+-----------------------------+-----------------------------+-----------------------------+
+   | ``Current [A]``      | Yes       | ``Current(*)``         | ``I/*``                     | ``Current (*)``             | ``Current``                 | ``I[*]``                    |
+   +----------------------+-----------+------------------------+-----------------------------+-----------------------------+-----------------------------+-----------------------------+
+   | ``Voltage [V]``      | Yes       | ``Voltage(*)``         | ``Ecell/*``                 | ``Voltage (*)``             | ``Voltage``                 | ``U[*]``                    |
+   +----------------------+-----------+------------------------+-----------------------------+-----------------------------+-----------------------------+-----------------------------+
+   | ``Capacity [Ah]``    | Yes       | ``Chg. Cap.(*)``,      | ``Q charge/*``,             | ``Charge Capacity (*)``,    | ``Capacity``                | ``Ah[*]``                   |
+   |                      |           | ``DChg. Cap.(*)``      | ``Q discharge/*``           | ``Discharge Capacity (*)``  |                             |                             |
+   +----------------------+-----------+------------------------+-----------------------------+-----------------------------+-----------------------------+-----------------------------+
+   | ``Temperature [C]``  | No        | ``T1(*)``              | ``Temperature/*``           | ``Aux_Temperature_1 (*)``   | ``Temp 1``                  | ``T1[*]``                   |
+   +----------------------+-----------+------------------------+-----------------------------+-----------------------------+-----------------------------+-----------------------------+
+
+.. raw:: html
+
+   </div>
+
+Where no units are provided (as is the case with Maccor), the PyProBE default units are
+assumed.
 
 The columns marked with *Auto from ...* are automatically generated by the PyProBE 
 data import process. This process includes automatic unit conversion to the PyProBE
diff --git a/pyprobe/analysis/base/degradation_mode_analysis_functions.py b/pyprobe/analysis/base/degradation_mode_analysis_functions.py
index 1b34261..0e410d1 100644
--- a/pyprobe/analysis/base/degradation_mode_analysis_functions.py
+++ b/pyprobe/analysis/base/degradation_mode_analysis_functions.py
@@ -59,7 +59,7 @@ def ocv_curve_fit(
         return np.sum((model - fitting_target_data) ** 2)
 
     if optimizer == "minimize":
-        fitting_result = opt.minimize(cost_function, x_guess, bounds=[(0, 1)] * 4)
+        fitting_result = opt.minimize(cost_function, x_guess)
     elif optimizer == "differential_evolution":
         fitting_result = opt.differential_evolution(
             cost_function, bounds=[(0.75, 0.95), (0.2, 0.3), (0, 0.05), (0.85, 0.95)]
diff --git a/pyprobe/analysis/degradation_mode_analysis.py b/pyprobe/analysis/degradation_mode_analysis.py
index 71c95ad..df8dbca 100644
--- a/pyprobe/analysis/degradation_mode_analysis.py
+++ b/pyprobe/analysis/degradation_mode_analysis.py
@@ -42,8 +42,7 @@ class DMA(BaseModel):
     ) -> Tuple[Result, Result]:
         """Fit half cell open circuit potential curves to full cell OCV data.
 
-        Based on the code from :footcite:t:`Kirkaldy_batteryDAT_2023`. The fitting
-        algorithm is based on the scipy.optimize.minimize function.
+        Based on the code from :footcite:t:`Kirkaldy_batteryDAT_2023`.
 
         Args:
             x_ne (NDArray[np.float64]): The anode stoichiometry data.
@@ -51,13 +50,6 @@ class DMA(BaseModel):
             ocp_ne (NDArray[np.float64]): The anode OCP data.
             ocp_pe (NDArray[np.float64]): The cathode OCP data.
             x_guess (NDArray[np.float64]): The initial guess for the fit.
-            fitting_target (str, optional):
-                The target for the curve fitting. Defaults to "OCV".
-            optimizer (str, optional):
-                The optimization algorithm to use. Defaults to None. If None, the
-                optimizer will be set to scipy.optimize.minimize for
-                fitting_target="OCV" and scipy.optimize.differential_evolution for
-                fitting_target="dQdV" or "dVdQ".
 
         Returns:
             Tuple[Result, Result]:
diff --git a/pyprobe/cell.py b/pyprobe/cell.py
index 8e5fd1a..892e572 100644
--- a/pyprobe/cell.py
+++ b/pyprobe/cell.py
@@ -8,7 +8,7 @@ import distinctipy
 import polars as pl
 from pydantic import BaseModel, Field, field_validator, validate_call
 
-from pyprobe.cyclers import basecycler, biologic, neware
+from pyprobe.cyclers import arbin, basecycler, basytec, biologic, maccor, neware
 from pyprobe.filters import Procedure
 from pyprobe.readme_processor import process_readme
 
@@ -98,11 +98,14 @@ class Cell(BaseModel):
             "neware": neware.Neware,
             "biologic": biologic.Biologic,
             "biologic_MB": biologic.BiologicMB,
+            "arbin": arbin.Arbin,
+            "maccor": maccor.Maccor,
+            "basytec": basytec.Basytec,
         }
         t1 = time.time()
         importer = cycler_dict[cycler](input_data_path=input_data_path)
         self._write_parquet(importer, output_data_path)
-        print(f"\tparquet written in {time.time()-t1:.2f} seconds.")
+        print(f"\tparquet written in {time.time()-t1: .2f} seconds.")
 
     @validate_call
     def process_generic_file(
@@ -150,7 +153,7 @@ class Cell(BaseModel):
             column_dict=column_dict,
         )
         self._write_parquet(importer, output_data_path)
-        print(f"\tparquet written in {time.time()-t1:.2f} seconds.")
+        print(f"\tparquet written in {time.time()-t1: .2f} seconds.")
 
     @validate_call
     def add_procedure(
diff --git a/pyprobe/cyclers/arbin.py b/pyprobe/cyclers/arbin.py
new file mode 100644
index 0000000..956ea4f
--- /dev/null
+++ b/pyprobe/cyclers/arbin.py
@@ -0,0 +1,21 @@
+"""A module to load and process Arbin battery cycler data."""
+
+
+from pyprobe.cyclers.basecycler import BaseCycler
+
+
+class Arbin(BaseCycler):
+    """A class to load and process Neware battery cycler data."""
+
+    input_data_path: str
+    column_dict: dict[str, str] = {
+        "Date Time": "Date",
+        "Test Time (*)": "Time [*]",
+        "Step Index": "Step",
+        "Current (*)": "Current [*]",
+        "Voltage (*)": "Voltage [*]",
+        "Charge Capacity (*)": "Charge Capacity [*]",
+        "Discharge Capacity (*)": "Discharge Capacity [*]",
+        "Aux_Temperature_1 (*)": "Temperature [*]",
+    }
+    datetime_format: str = "%d/%m/%Y %H:%M:%S%.f"
diff --git a/pyprobe/cyclers/basecycler.py b/pyprobe/cyclers/basecycler.py
index 7a2c786..8a5d077 100644
--- a/pyprobe/cyclers/basecycler.py
+++ b/pyprobe/cyclers/basecycler.py
@@ -3,10 +3,10 @@
 import glob
 import os
 import warnings
-from typing import Any, Dict, List, Optional
+from typing import Dict, List, Optional
 
 import polars as pl
-from pydantic import BaseModel
+from pydantic import BaseModel, field_validator, model_validator
 
 from pyprobe.units import Units
 
@@ -19,13 +19,132 @@ class BaseCycler(BaseModel):
     column_dict: Dict[str, str]
     """A dictionary mapping the column name format of the cycler to the PyProBE format.
     Units are indicated by an asterisk (*)."""
+    datetime_format: Optional[str] = None
+    """The string format of the date column if present. See the
+    `chrono crate <https://docs.rs/chrono/latest/chrono/format/strftime/index.html>`_
+    documentation for more information on the format string.
+    """
 
-    def model_post_init(self, __context: Any) -> None:
-        """Post initialization method for the BaseModel."""
+    @field_validator("input_data_path")
+    @classmethod
+    def _check_input_data_path(cls, value: str) -> str:
+        """Check if the input data path is valid.
+
+        Args:
+            value (str): The input data path.
+
+        Returns:
+            str: The input data path.
+        """
+        if "*" in value:
+            files = glob.glob(value)
+            if len(files) == 0:
+                raise ValueError(f"No files found with the pattern {value}.")
+        elif not os.path.exists(value):
+            raise ValueError(f"File not found: path {value} does not exist.")
+        return value
+
+    @field_validator("column_dict")
+    @classmethod
+    def _check_column_dict(cls, value: Dict[str, str]) -> Dict[str, str]:
+        """Check if the column dictionary is valid.
+
+        Args:
+            value (Dict[str, str]): The column dictionary.
+
+        Returns:
+            Dict[str, str]: The column dictionary.
+        """
+        pyprobe_data_columns = {value for value in value.values()}
+        pyprobe_required_columns = {
+            "Time [*]",
+            "Current [*]",
+            "Voltage [*]",
+            "Step",
+            "Capacity [*]",
+        }
+        missing_columns = pyprobe_required_columns - pyprobe_data_columns
+        extra_error_message = ""
+        if "Capacity [*]" in missing_columns:
+            if {"Charge Capacity [*]", "Discharge Capacity [*]"}.issubset(
+                pyprobe_data_columns
+            ):
+                missing_columns.remove("Capacity [*]")
+            else:
+                missing_columns.add("Charge Capacity [*]")
+                missing_columns.add("Discharge Capacity [*]")
+                extra_error_message = (
+                    " Capacity can be specified as 'Capacity [*]' or "
+                    "'Charge Capacity [*]' and 'Discharge Capacity [*]'."
+                )
+        if len(missing_columns) > 0:
+            raise ValueError(
+                f"The column dictionary is missing one or more required columns: "
+                f"{missing_columns}." + extra_error_message
+            )
+        return value
+
+    @model_validator(mode="after")
+    def import_and_validate_data(self) -> "BaseCycler":
+        """Import the data and validate the column mapping."""
         dataframe_list = self._get_dataframe_list()
         self._imported_dataframe = self.get_imported_dataframe(dataframe_list)
-        self._dataframe_columns = self._imported_dataframe.collect_schema().names()
-        self._column_map = self._map_columns(self.column_dict, self._dataframe_columns)
+        self._column_map = self._map_columns(
+            self.column_dict, self._imported_dataframe.collect_schema().names()
+        )
+        self._check_missing_columns(self.column_dict, self._column_map)
+        return self
+
+    @staticmethod
+    def _check_missing_columns(
+        column_dict: Dict[str, str], column_map: Dict[str, Dict[str, str | pl.DataType]]
+    ) -> None:
+        """Check for missing columns in the imported data.
+
+        Args:
+            column_map (Dict[str, Dict[str, str | pl.DataType]]):
+                A dictionary mapping the column name format of the cycler to the PyProBE
+                format.
+
+        Raises:
+            ValueError:
+                If any of ["Time", "Current", "Voltage", "Capacity", "Step"]
+                are missing.
+        """
+        pyprobe_required_columns = set(
+            [
+                "Time",
+                "Current",
+                "Voltage",
+                "Capacity",
+                "Step",
+            ]
+        )
+        missing_columns = pyprobe_required_columns - set(column_map.keys())
+        if missing_columns:
+            if "Capacity" in missing_columns:
+                if (
+                    "Charge Capacity" in column_map.keys()
+                    and "Discharge Capacity" in column_map.keys()
+                ):
+                    missing_columns.remove("Capacity")
+                else:
+                    missing_columns.add("Charge Capacity")
+                    missing_columns.add("Discharge Capacity")
+        if len(missing_columns) > 0:
+            search_names = []
+            for column in missing_columns:
+                if column != "Step":
+                    full_name = column + " [*]"
+                else:
+                    full_name = column
+                for cycler_name, pyprobe_name in column_dict.items():
+                    if pyprobe_name == full_name:
+                        search_names.append(cycler_name)
+            raise ValueError(
+                f"PyProBE cannot find the following columns, please check your data: "
+                f"{search_names}."
+            )
 
     @staticmethod
     def read_file(filepath: str) -> pl.DataFrame | pl.LazyFrame:
@@ -119,16 +238,47 @@ class BaseCycler(BaseModel):
     def _map_columns(
         cls, column_dict: Dict[str, str], dataframe_columns: List[str]
     ) -> Dict[str, Dict[str, str | pl.DataType]]:
-        """Map the columns of the imported dataframe to the PyProBE format."""
+        """Map the columns of the imported dataframe to the PyProBE format.
+
+        Args:
+            column_dict (Dict[str, str]):
+                A dictionary mapping the column name format of the cycler to the PyProBE
+                format.
+            dataframe_columns (List[str]): The columns of the imported dataframe.
+
+        Returns:
+            Dict[str, Dict[str, str | pl.DataType]]:
+                A dictionary mapping the column name format of the cycler to the PyProBE
+                format.
+
+                Fields (for each quantity):
+                    Cycler column name (str): The name of the column in the cycler data.
+                    PyProBE column name (str):
+                        The name of the column in the PyProBE data.
+                    Unit (str): The unit of the column.
+                    Type (pl.DataType): The data type of the column.
+        """
         column_map: Dict[str, Dict[str, str | pl.DataType]] = {}
         for cycler_format, pyprobe_format in column_dict.items():
             for cycler_column_name in dataframe_columns:
                 unit = cls._match_unit(cycler_column_name, cycler_format)
                 if unit is not None:
                     quantity = pyprobe_format.replace(" [*]", "")
-                    if quantity == "Temperature":
-                        if unit != "K":
-                            unit = "C"
+                    default_units = {
+                        "Time": "s",
+                        "Current": "A",
+                        "Voltage": "V",
+                        "Capacity": "Ah",
+                        "Charge Capacity": "Ah",
+                        "Discharge Capacity": "Ah",
+                        "Temperature": "C",
+                    }
+
+                    if quantity == "Temperature" and unit != "K":
+                        unit = "C"
+                    elif unit == "" and quantity in default_units:
+                        unit = default_units[quantity]
+
                     pyprobe_column_name = pyprobe_format.replace("*", unit)
 
                     column_map[quantity] = {}
@@ -143,6 +293,37 @@ class BaseCycler(BaseModel):
                         column_map[quantity]["Type"] = pl.Float64
         return column_map
 
+    def _assign_instructions(self) -> None:
+        instruction_dict = {
+            "Date": self.date,
+            "Time": self.time,
+            "Current": self.current,
+            "Voltage": self.voltage,
+            "Capacity": self.capacity,
+            "Temperature": self.temperature,
+            "Step": self.step,
+            "Cycle": self.cycle,
+            "Event": self.event,
+        }
+        for quantity in self._column_map.keys():
+            self._column_map[quantity]["Instruction"] = instruction_dict[quantity]
+
+    @staticmethod
+    def _tabulate_column_map(
+        column_map: Dict[str, Dict[str, str | pl.DataType]]
+    ) -> str:
+        data = {
+            "Quantity": list(column_map.keys()),
+            "Cycler column name": [
+                v["Cycler column name"] for v in column_map.values()
+            ],
+            "PyProBE column name": [
+                v["PyProBE column name"] for v in column_map.values()
+            ],
+        }
+
+        return pl.DataFrame(data)
+
     def _convert_names(self, quantity: str) -> pl.Expr:
         """Write a column in the PyProBE column name format and convert its type.
 
@@ -170,15 +351,17 @@ class BaseCycler(BaseModel):
             pl.DataFrame: The DataFrame.
         """
         required_columns = [
-            self.date,
+            self.date if "Date" in self._column_map.keys() else None,
             self.time,
             self.cycle,
             self.step,
             self.event,
             self.current,
             self.voltage,
-            self.capacity,
-            self.temperature,
+            self.capacity
+            if "Capacity" in self._column_map.keys()
+            else self.capacity_from_ch_dch,
+            self.temperature if "Temperature" in self._column_map.keys() else None,
         ]
         name_converters = [
             self._convert_names(quantity) for quantity in self._column_map.keys()
@@ -188,16 +371,15 @@ class BaseCycler(BaseModel):
         return imported_dataframe.select(required_columns)
 
     @property
-    def date(self) -> Optional[pl.Expr]:
+    def date(self) -> pl.Expr:
         """Identify and format the date column.
 
         Returns:
-            Optional[pl.Expr]: A polars expression for the date column.
+            pl.Expr: A polars expression for the date column.
         """
-        if "Date" in self._column_map.keys():
-            return pl.col("Date").str.to_datetime(time_unit="us")
-        else:
-            return None
+        return pl.col("Date").str.to_datetime(
+            format=self.datetime_format, time_unit="us"
+        )
 
     @property
     def time(self) -> pl.Expr:
@@ -276,28 +458,18 @@ class BaseCycler(BaseModel):
         Returns:
             pl.Expr: A polars expression for the capacity column.
         """
-        if "Capacity" in self._column_map.keys():
-            return Units(
-                "Capacity", self._column_map["Capacity"]["Unit"]
-            ).to_default_unit()
-        else:
-            return self.capacity_from_ch_dch
+        return Units("Capacity", self._column_map["Capacity"]["Unit"]).to_default_unit()
 
     @property
-    def temperature(self) -> Optional[pl.Expr]:
+    def temperature(self) -> pl.Expr:
         """Identify and format the temperature column.
 
-        An optional column, if not found, a column of None values is returned.
-
         Returns:
-            Optional[pl.Expr]: A polars expression for the temperature column.
+            pl.Expr: A polars expression for the temperature column.
         """
-        if "Temperature" in self._column_map.keys():
-            return Units(
-                "Temperature", self._column_map["Temperature"]["Unit"]
-            ).to_default_unit()
-        else:
-            return None
+        return Units(
+            "Temperature", self._column_map["Temperature"]["Unit"]
+        ).to_default_unit()
 
     @property
     def step(self) -> pl.Expr:
diff --git a/pyprobe/cyclers/basytec.py b/pyprobe/cyclers/basytec.py
new file mode 100644
index 0000000..7b8ee00
--- /dev/null
+++ b/pyprobe/cyclers/basytec.py
@@ -0,0 +1,58 @@
+"""A module to load and process Basytec battery cycler data."""
+
+
+from datetime import datetime
+
+import polars as pl
+
+from pyprobe.cyclers.basecycler import BaseCycler
+
+
+class Basytec(BaseCycler):
+    """A class to load and process Basytec battery cycler data."""
+
+    input_data_path: str
+    column_dict: dict[str, str] = {
+        "Date": "Date",
+        "~Time[*]": "Time [*]",
+        "Line": "Step",
+        "I[*]": "Current [*]",
+        "U[*]": "Voltage [*]",
+        "Ah[*]": "Capacity [*]",
+        "T1[*]": "Temperature [*]",
+    }
+
+    @staticmethod
+    def read_file(filepath: str) -> pl.DataFrame | pl.LazyFrame:
+        """Read a battery cycler file into a DataFrame.
+
+        Args:
+            filepath: The path to the file.
+
+        Returns:
+            pl.DataFrame | pl.LazyFrame: The DataFrame.
+        """
+        n_header_lines = 0
+        with open(filepath, "r", encoding="utf-8") as file:
+            for line in file:
+                if line.startswith("~"):
+                    n_header_lines += 1
+                    if line.startswith("~Start of Test"):
+                        start_time_line = line
+
+        _, value = start_time_line.split(": ")
+        start_time = datetime.strptime(value.strip(), "%d.%m.%Y %H:%M:%S")
+
+        dataframe = pl.scan_csv(
+            filepath, skip_rows=n_header_lines - 1, separator="\t", infer_schema=False
+        )
+
+        dataframe = dataframe.with_columns(
+            (
+                (pl.col("~Time[s]").cast(pl.Float64) * 1000000).cast(pl.Duration)
+                + pl.lit(start_time)
+            )
+            .cast(str)
+            .alias("Date")
+        )
+        return dataframe
diff --git a/pyprobe/cyclers/biologic.py b/pyprobe/cyclers/biologic.py
index 7f6120a..19d667e 100644
--- a/pyprobe/cyclers/biologic.py
+++ b/pyprobe/cyclers/biologic.py
@@ -18,6 +18,7 @@ class Biologic(BaseCycler):
         "time/*": "Time [*]",
         "Ns": "Step",
         "I/*": "Current [*]",
+        "<I>/*": "Current [*]",
         "Ecell/*": "Voltage [*]",
         "Q charge/*": "Charge Capacity [*]",
         "Q discharge/*": "Discharge Capacity [*]",
@@ -60,15 +61,6 @@ class Biologic(BaseCycler):
         )
         return dataframe
 
-    @property
-    def step(self) -> pl.Expr:
-        """Identify the step number.
-
-        Returns:
-            pl.Expr: A polars expression for the step number.
-        """
-        return pl.col("Step") + 1
-
 
 class BiologicMB(Biologic):
     """A class to load and process Biologic Modulo Bat  battery cycler data."""
diff --git a/pyprobe/cyclers/maccor.py b/pyprobe/cyclers/maccor.py
new file mode 100644
index 0000000..a424cb6
--- /dev/null
+++ b/pyprobe/cyclers/maccor.py
@@ -0,0 +1,96 @@
+"""A module to load and process Maccor battery cycler data."""
+
+
+import polars as pl
+
+from pyprobe.cyclers.basecycler import BaseCycler
+
+
+class Maccor(BaseCycler):
+    """A class to load and process Neware battery cycler data."""
+
+    input_data_path: str
+    column_dict: dict[str, str] = {
+        "DPT Time": "Date",
+        "Test Time (sec)": "Time [*]",
+        "Step": "Step",
+        "Current": "Current [*]",
+        "Voltage": "Voltage [*]",
+        "Capacity": "Capacity [*]",
+        "Temp 1": "Temperature [*]",
+    }
+    datetime_format: str = "%d-%b-%y %I:%M:%S %p"
+
+    @staticmethod
+    def read_file(filepath: str) -> pl.DataFrame | pl.LazyFrame:
+        """Read a battery cycler file into a DataFrame.
+
+        Args:
+            filepath: The path to the file.
+
+        Returns:
+            pl.DataFrame | pl.LazyFrame: The DataFrame.
+        """
+        dataframe = pl.scan_csv(filepath, skip_rows=2, infer_schema=False)
+        return dataframe
+
+    @property
+    def date(self) -> pl.Expr:
+        """Identify and format the date column.
+
+        For the Maccor cycler, this takes the first date in the file and adds the time
+        column to it.
+
+        Returns:
+            pl.Expr: A polars expression for the date column.
+        """
+        if "Date" in self._column_map.keys():
+            date_col = pl.col("Date").str.to_datetime(
+                format=self.datetime_format, time_unit="us"
+            )
+            return (
+                (pl.col("Time [s]").cast(pl.Float64) * 1000000).cast(pl.Duration)
+                + date_col.first()
+            ).alias("Date")
+        else:
+            return None
+
+    @property
+    def charge_capacity(self) -> pl.Expr:
+        """Identify and format the charge capacity column.
+
+        For the Maccor cycler, this is the capacity column when the current is positive.
+
+        Returns:
+            pl.Expr: A polars expression for the charge capacity column.
+        """
+        current_direction = self.current.sign()
+        charge_capacity = pl.col("Capacity [Ah]") * current_direction.replace(-1, 0)
+        return charge_capacity.alias("Charge Capacity [Ah]")
+
+    @property
+    def discharge_capacity(self) -> pl.Expr:
+        """Identify and format the discharge capacity column.
+
+        For the Maccor cycler, this is the capacity column when the current is negative.
+
+        Returns:
+            pl.Expr: A polars expression for the discharge capacity column.
+        """
+        current_direction = self.current.sign()
+        charge_capacity = (
+            pl.col("Capacity [Ah]") * current_direction.replace(1, 0).abs()
+        )
+        return charge_capacity.alias("Discharge Capacity [Ah]")
+
+    @property
+    def capacity(self) -> pl.Expr:
+        """Identify and format the capacity column.
+
+        For the Maccor cycler remove the option to calculate the capacity from a single
+        capacity column.
+
+        Returns:
+            pl.Expr: A polars expression for the capacity column.
+        """
+        return self.capacity_from_ch_dch
diff --git a/pyprobe/cyclers/neware.py b/pyprobe/cyclers/neware.py
index 9afcdef..fad5105 100644
--- a/pyprobe/cyclers/neware.py
+++ b/pyprobe/cyclers/neware.py
@@ -4,6 +4,7 @@
 import polars as pl
 
 from pyprobe.cyclers.basecycler import BaseCycler
+from pyprobe.units import Units
 
 
 class Neware(BaseCycler):
@@ -18,27 +19,116 @@ class Neware(BaseCycler):
         "Chg. Cap.(*)": "Charge Capacity [*]",
         "DChg. Cap.(*)": "Discharge Capacity [*]",
         "T1(*)": "Temperature [*]",
+        "Total Time": "Time [*]",
+        "Capacity(*)": "Capacity [*]",
     }
 
+    @staticmethod
+    def read_file(filepath: str) -> pl.DataFrame | pl.LazyFrame:
+        """Read a battery cycler file into a DataFrame.
+
+        Args:
+            filepath (str): The path to the file.
+
+        Returns:
+            pl.DataFrame | pl.LazyFrame: The DataFrame.
+        """
+        dataframe = BaseCycler.read_file(filepath)
+        if "Time" in dataframe.collect_schema().names():
+            dataframe = dataframe.with_columns(
+                pl.col("Time")
+                .str.strptime(pl.Datetime, format="%Y-%m-%d %H:%M:%S%.f")
+                .cast(pl.Float64)
+                .alias("Time")
+            )
+            dataframe = dataframe.with_columns(
+                pl.col("Time") - pl.col("Time").first().alias("Time")
+            )
+            dataframe = dataframe.with_columns(pl.col("Time") / 1e6)
+        if "Total Time" in dataframe.collect_schema().names():
+            dataframe = dataframe.with_columns(
+                pl.col("Total Time")
+                .str.strptime(pl.Datetime, format="%Y-%m-%d %H:%M:%S%.f")
+                .cast(pl.Float64)
+                .alias("Total Time")
+            )
+            dataframe = dataframe.with_columns(
+                pl.col("Total Time") - pl.col("Total Time").first().alias("Total Time")
+            )
+            dataframe = dataframe.with_columns(pl.col("Total Time") / 1e6)
+        return dataframe
+
     @property
     def time(self) -> pl.Expr:
         """Identify and format the time column.
 
+        For Neware data, by default the time column is calculated from the "Date"
+        column if it exists.
+
         Returns:
             pl.Expr: A polars expression for the time column.
         """
-        if (
-            self._imported_dataframe.dtypes[
-                self._imported_dataframe.columns.index("Date")
-            ]
-            != pl.Datetime
-        ):
-            date = pl.col("Date").str.to_datetime().alias("Date")
+        if "Date" in self._column_map.keys():
+            return (
+                (self.date.diff().dt.total_microseconds().cum_sum() / 1e6)
+                .fill_null(strategy="zero")
+                .alias("Time [s]")
+            )
+        else:
+            return pl.col("Time [s]")
+
+    @property
+    def charge_capacity(self) -> pl.Expr:
+        """Identify and format the charge capacity column.
+
+        For the Neware cycler, this is either the "Chg. Cap.(*)" column or the
+        "Capacity(*)" column when the current is positive.
+
+        Returns:
+            pl.Expr: A polars expression for the charge capacity column.
+        """
+        if "Charge Capacity" in self._column_map.keys():
+            return super().charge_capacity
         else:
-            date = pl.col("Date")
+            current_direction = self.current.sign()
+            charge_capacity = (
+                Units(
+                    "Capacity", self._column_map["Capacity"]["Unit"]
+                ).to_default_unit()
+                * current_direction.replace(-1, 0).abs()
+            )
+            return charge_capacity.alias("Charge Capacity [Ah]")
 
-        return (
-            (date.diff().dt.total_microseconds().cum_sum() / 1e6)
-            .fill_null(strategy="zero")
-            .alias("Time [s]")
-        )
+    @property
+    def discharge_capacity(self) -> pl.Expr:
+        """Identify and format the discharge capacity column.
+
+        For the Neware cycler, this is either the "DChg. Cap.(*)" column or the
+        "Capacity(*)" column when the current is negative.
+
+        Returns:
+            pl.Expr: A polars expression for the discharge capacity column.
+        """
+        if "Discharge Capacity" in self._column_map.keys():
+            return super().discharge_capacity
+        else:
+            current_direction = self.current.sign()
+            discharge_capacity = (
+                Units(
+                    "Capacity", self._column_map["Capacity"]["Unit"]
+                ).to_default_unit()
+                * current_direction.replace(1, 0).abs()
+            )
+            return discharge_capacity.alias("Discharge Capacity [Ah]")
+
+    @property
+    def capacity(self) -> pl.Expr:
+        """Identify and format the capacity column.
+
+        For the Neware cycler remove the option to calculate the capacity from a single
+        capacity column.
+
+        Returns:
+            pl.Expr: A polars expression for the capacity column.
+        """
+        return self.capacity_from_ch_dch
diff --git a/pyproject.toml b/pyproject.toml
index 9c4762d..7d40b54 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -9,7 +9,7 @@ exclude = [
 
 [project]
 name = "PyProBE"
-version = "1.0.3"
+version = "1.0.2"
 requires-python = ">=3.11"
 dependencies = [
     "numpy", # for numerical operations


Test patch:
diff --git a/tests/cyclers/test_arbin.py b/tests/cyclers/test_arbin.py
new file mode 100644
index 0000000..3e2d5c2
--- /dev/null
+++ b/tests/cyclers/test_arbin.py
@@ -0,0 +1,30 @@
+"""Tests for the Arbin cycler class."""
+
+from pyprobe.cyclers.arbin import Arbin
+
+
+def test_read_and_process(benchmark):
+    """Test the full process of reading and processing a file."""
+    arbin_cycler = Arbin(
+        input_data_path="tests/sample_data/arbin/sample_data_arbin.csv"
+    )
+
+    def read_and_process():
+        return arbin_cycler.pyprobe_dataframe
+
+    pyprobe_dataframe = benchmark(read_and_process)
+    expected_columns = [
+        "Date",
+        "Time [s]",
+        "Step",
+        "Cycle",
+        "Event",
+        "Current [A]",
+        "Voltage [V]",
+        "Capacity [Ah]",
+        "Temperature [C]",
+    ]
+    assert set(pyprobe_dataframe.columns) == set(expected_columns)
+    assert set(
+        pyprobe_dataframe.select("Event").unique().collect().to_series().to_list()
+    ) == set([0, 1, 2])
diff --git a/tests/cyclers/test_basecycler.py b/tests/cyclers/test_basecycler.py
index 17214d3..57537d7 100644
--- a/tests/cyclers/test_basecycler.py
+++ b/tests/cyclers/test_basecycler.py
@@ -1,6 +1,7 @@
 """Test the basecycler module."""
 import copy
 import os
+import re
 
 import polars as pl
 import polars.testing as pl_testing
@@ -77,21 +78,10 @@ def sample_cycler_instance(sample_dataframe, column_dict):
     )
 
 
-def test_map_columns(column_dict):
-    """Test initialising the basecycler."""
-    # test with single file
-    dict_with_extra = copy.deepcopy(column_dict)
-    dict_with_extra["Ecell [*]"] = "Voltage [*]"
-    column_list = [
-        "DateTime",
-        "T [s]",
-        "V [V]",
-        "I [mA]",
-        "Q [Ah]",
-        "Count",
-        "Temp [C]",
-    ]
-    expected_map = {
+@pytest.fixture
+def sample_column_map():
+    """A sample column map."""
+    return {
         "Date": {
             "Cycler column name": "DateTime",
             "PyProBE column name": "Date",
@@ -136,6 +126,64 @@ def test_map_columns(column_dict):
         },
     }
 
+
+def test_input_data_path_validator():
+    """Test the input data path validator."""
+    # test with invalid path
+    path = "invalid_path"
+    with pytest.raises(ValueError, match=f"File not found: path {path} does not exist"):
+        BaseCycler._check_input_data_path(path)
+
+    path = "invalid_path*"
+    with pytest.raises(ValueError, match=f"No files found with the pattern {path}."):
+        BaseCycler._check_input_data_path(path)
+
+    # test with valid path
+    assert (
+        BaseCycler._check_input_data_path(
+            "tests/sample_data/neware/sample_data_neware.csv"
+        )
+        == "tests/sample_data/neware/sample_data_neware.csv"
+    )
+
+
+def test_column_dict_validator(column_dict):
+    """Test the column dictionary validator."""
+    # test with missing columns
+    column_dict.pop("I [*]")
+    column_dict.pop("Q [*]")
+    expected_message = re.escape(
+        "The column dictionary is missing one or more required columns: "
+        "{'Current [*]'}."
+    )
+
+    with pytest.raises(ValueError, match=expected_message):
+        BaseCycler._check_column_dict(column_dict)
+
+    column_dict.pop("Q_ch [*]")
+    column_dict.pop("Q_dis [*]")
+    with pytest.raises(
+        ValueError,
+    ):
+        BaseCycler._check_column_dict(column_dict)
+
+
+def test_map_columns(column_dict, sample_column_map):
+    """Test initialising the basecycler."""
+    # test with single file
+    dict_with_extra = copy.deepcopy(column_dict)
+    dict_with_extra["Ecell [*]"] = "Voltage [*]"
+    column_list = [
+        "DateTime",
+        "T [s]",
+        "V [V]",
+        "I [mA]",
+        "Q [Ah]",
+        "Count",
+        "Temp [C]",
+    ]
+    expected_map = sample_column_map
+
     assert BaseCycler._map_columns(dict_with_extra, column_list) == expected_map
 
     # missing columns
@@ -144,6 +192,53 @@ def test_map_columns(column_dict):
     assert BaseCycler._map_columns(dict_with_extra, column_list) == expected_map
 
 
+def test_check_missing_columns(sample_column_map, column_dict):
+    """Test the check missing columns method."""
+    sample_column_map.pop("Current")
+    expected_message = (
+        "PyProBE cannot find the following columns, please check your data: ['I [*]']."
+    )
+    with pytest.raises(ValueError, match=re.escape(expected_message)):
+        BaseCycler._check_missing_columns(column_dict, sample_column_map)
+
+
+def test_tabulate_column_map(sample_column_map):
+    """Test tabulating the column map."""
+    column_map_table = BaseCycler._tabulate_column_map(sample_column_map)
+    expected_dataframe = pl.DataFrame(
+        {
+            "Quantity": [
+                "Date",
+                "Time",
+                "Voltage",
+                "Current",
+                "Capacity",
+                "Step",
+                "Temperature",
+            ],
+            "Cycler column name": [
+                "DateTime",
+                "T [s]",
+                "V [V]",
+                "I [mA]",
+                "Q [Ah]",
+                "Count",
+                "Temp [C]",
+            ],
+            "PyProBE column name": [
+                "Date",
+                "Time [s]",
+                "Voltage [V]",
+                "Current [mA]",
+                "Capacity [Ah]",
+                "Step",
+                "Temperature [C]",
+            ],
+        }
+    )
+    pl_testing.assert_frame_equal(column_map_table, expected_dataframe)
+
+
 def test_init(sample_cycler_instance, sample_dataframe):
     """Test initialising the basecycler."""
     df = sample_dataframe.with_columns(
diff --git a/tests/cyclers/test_basytec.py b/tests/cyclers/test_basytec.py
new file mode 100644
index 0000000..761f808
--- /dev/null
+++ b/tests/cyclers/test_basytec.py
@@ -0,0 +1,52 @@
+"""Tests for the Basytec cycler class."""
+
+from datetime import datetime
+
+import polars as pl
+from polars.testing import assert_frame_equal
+
+from pyprobe.cyclers.basytec import Basytec
+
+
+def test_read_file_basytec():
+    """Test reading a Basytec file."""
+    dataframe = Basytec.read_file(
+        "tests/sample_data/basytec/sample_data_basytec.txt"
+    ).collect()
+    assert "Date" in dataframe.columns
+    assert dataframe["Date"][0] == "2023-06-19 17:56:53.000000"
+    assert dataframe["Date"][2] == "2023-06-19 17:56:54.002823"
+
+
+def test_read_and_process_basytec():
+    """Test the full process of reading and processing a file."""
+    basytec_cycler = Basytec(
+        input_data_path="tests/sample_data/basytec/sample_data_basytec.txt"
+    )
+    pyprobe_dataframe = basytec_cycler.pyprobe_dataframe
+    expected_columns = [
+        "Date",
+        "Time [s]",
+        "Step",
+        "Cycle",
+        "Event",
+        "Current [A]",
+        "Voltage [V]",
+        "Capacity [Ah]",
+        "Temperature [C]",
+    ]
+    assert set(pyprobe_dataframe.columns) == set(expected_columns)
+    last_row = pl.LazyFrame(
+        {
+            "Date": datetime(2023, 6, 19, 17, 58, 3, 235803),
+            "Time [s]": [70.235804],
+            "Cycle": [0],
+            "Step": [4],
+            "Event": [1],
+            "Current [A]": [0.449602],
+            "Voltage [V]": [3.53285],
+            "Capacity [Ah]": [0.001248916998009],
+            "Temperature [C]": [25.47953],
+        }
+    )
+    assert_frame_equal(pyprobe_dataframe.tail(1), last_row)
diff --git a/tests/cyclers/test_biologic.py b/tests/cyclers/test_biologic.py
index e429325..9e94aa2 100644
--- a/tests/cyclers/test_biologic.py
+++ b/tests/cyclers/test_biologic.py
@@ -93,7 +93,7 @@ def test_read_and_process(benchmark, biologic_cycler, biologic_MB_cycler):
     steps = list(
         pyprobe_dataframe.select(pl.col("Step")).collect().unique().to_numpy().flatten()
     )
-    assert set(steps) == set([1, 2])
+    assert set(steps) == set([0, 1])
 
     pyprobe_dataframe = biologic_MB_cycler.pyprobe_dataframe
     expected_columns = [
@@ -121,7 +121,7 @@ def test_read_and_process(benchmark, biologic_cycler, biologic_MB_cycler):
     steps = list(
         pyprobe_dataframe.select(pl.col("Step")).collect().unique().to_numpy().flatten()
     )
-    assert set(steps) == set([1, 2, 3, 4, 5, 6])
+    assert set(steps) == set([0, 1, 2, 3, 4, 5])
 
 
 def test_process_dataframe(monkeypatch):
@@ -164,7 +164,7 @@ def test_process_dataframe(monkeypatch):
     expected_dataframe = pl.DataFrame(
         {
             "Time [s]": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
-            "Step": [1, 1, 2, 2, 2, 1, 1],
+            "Step": [0, 0, 1, 1, 1, 0, 0],
             "Current [A]": [1e-3, 2e-3, 3e-3, 4e-3, 0, 0, 0],
             "Voltage [V]": [4.0, 5.0, 6.0, 7.0, 0.0, 0.0, 0.0],
             "Capacity [Ah]": [0.020, 0.040, 0.030, 0.020, 0.020, 0.020, 0.020],
diff --git a/tests/cyclers/test_maccor.py b/tests/cyclers/test_maccor.py
new file mode 100644
index 0000000..dfbd758
--- /dev/null
+++ b/tests/cyclers/test_maccor.py
@@ -0,0 +1,127 @@
+"""Tests for the Maccor cycler class."""
+from datetime import datetime
+
+import polars as pl
+import pytest
+from polars.testing import assert_frame_equal
+
+from pyprobe.cyclers.maccor import Maccor
+
+
+def test_read_and_process_maccor():
+    """Test reading and processing a sample Maccor file."""
+    maccor_cycler = Maccor(
+        input_data_path="tests/sample_data/maccor/sample_data_maccor.csv"
+    )
+    pyprobe_dataframe = maccor_cycler.pyprobe_dataframe
+    expected_columns = [
+        "Date",
+        "Time [s]",
+        "Step",
+        "Cycle",
+        "Event",
+        "Current [A]",
+        "Voltage [V]",
+        "Capacity [Ah]",
+        "Temperature [C]",
+    ]
+    assert set(pyprobe_dataframe.columns) == set(expected_columns)
+    last_row = pl.LazyFrame(
+        {
+            "Date": datetime(2023, 11, 23, 15, 56, 24, 60000),
+            "Time [s]": [13.06],
+            "Cycle": [0],
+            "Step": [2],
+            "Event": [1],
+            "Current [A]": [28.798],
+            "Voltage [V]": [3.716],
+            "Capacity [Ah]": [0.048],
+            "Temperature [C]": [22.2591],
+        }
+    )
+    assert_frame_equal(pyprobe_dataframe.tail(1), last_row)
+
+
+@pytest.fixture
+def sample_dataframe():
+    """Return a sample DataFrame."""
+    return pl.DataFrame(
+        {
+            "Date": [
+                "01-Jan-21 11:00:00 PM",
+                "01-Jan-21 11:00:01 PM",
+                "01-Jan-21 11:00:02 PM",
+                "01-Jan-21 11:00:03 PM",
+                "01-Jan-21 11:00:04 PM",
+                "01-Jan-21 11:00:05 PM",
+            ],
+            "Time [s]": [0.0, 1.0, 2.0, 3.0, 4.0, 5.1],
+            "Capacity [Ah]": [1, 2, 3, 4, 5, 6],
+            "Current [A]": [1, 1, 0, -1, -1, 0],
+        }
+    )
+
+
+def test_date(sample_dataframe):
+    """Test the date property."""
+    maccor_cycler = Maccor(
+        input_data_path="tests/sample_data/maccor/sample_data_maccor.csv"
+    )
+    date = maccor_cycler.date
+    expected_dataframe = pl.DataFrame(
+        {
+            "Date": [
+                "2021-01-01 23:00:00.0",
+                "2021-01-01 23:00:01.0",
+                "2021-01-01 23:00:02.0",
+                "2021-01-01 23:00:03.0",
+                "2021-01-01 23:00:04.0",
+                "2021-01-01 23:00:05.1",
+            ]
+        }
+    ).with_columns(
+        pl.col("Date").str.to_datetime(format="%Y-%m-%d %H:%M:%S%.f", time_unit="us")
+    )
+    assert_frame_equal(sample_dataframe.select(date), expected_dataframe)
+
+
+def test_charge_capacity(sample_dataframe):
+    """Test the charge_capacity property."""
+    maccor_cycler = Maccor(
+        input_data_path="tests/sample_data/maccor/sample_data_maccor.csv"
+    )
+    charge_capacity = maccor_cycler.charge_capacity
+    assert_frame_equal(
+        sample_dataframe.select(charge_capacity),
+        pl.DataFrame({"Charge Capacity [Ah]": [1.0, 2.0, 0.0, 0.0, 0.0, 0.0]}),
+    )
+
+
+def test_discharge_capacity(sample_dataframe):
+    """Test the discharge_capacity property."""
+    maccor_cycler = Maccor(
+        input_data_path="tests/sample_data/maccor/sample_data_maccor.csv"
+    )
+    discharge_capacity = maccor_cycler.discharge_capacity
+    assert_frame_equal(
+        sample_dataframe.select(discharge_capacity),
+        pl.DataFrame({"Discharge Capacity [Ah]": [0.0, 0.0, 0.0, 4.0, 5.0, 0.0]}),
+    )
+
+
+def test_capacity(sample_dataframe):
+    """Test the capacity property."""
+    maccor_cycler = Maccor(
+        input_data_path="tests/sample_data/maccor/sample_data_maccor.csv"
+    )
+    capacity = maccor_cycler.capacity
+    dataframe = pl.DataFrame(
+        {
+            "Capacity [Ah]": [0.0, 20.0, 10.0, 20.0, 20.0, 20.0],
+            "Current [A]": [0.0, 3.0, -2.0, -2.0, 0.0, 0.0],
+        }
+    )
+    assert_frame_equal(
+        dataframe.select(capacity),
+        pl.DataFrame({"Capacity [Ah]": [20.0, 40.0, 30.0, 20.0, 20.0, 20.0]}),
+    )
diff --git a/tests/cyclers/test_neware.py b/tests/cyclers/test_neware.py
index 89de43a..d64fd05 100644
--- a/tests/cyclers/test_neware.py
+++ b/tests/cyclers/test_neware.py
@@ -22,6 +22,19 @@ def test_read_file(neware_cycler):
     )
     assert isinstance(unprocessed_dataframe, pl.DataFrame)
 
+    # Test that Time and Total time are read correctly
+    expected_start = pl.DataFrame(
+        {
+            "Time": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0],
+            "Total Time": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0],
+        }
+    )
+    pl_testing.assert_frame_equal(
+        neware_cycler._imported_dataframe.select("Time", "Total Time").head(6),
+        expected_start,
+    )
+    assert neware_cycler._imported_dataframe["Total Time"][-1] == 562784.5
+
 
 def test_sort_files(neware_cycler):
     """Test the _sort_files method."""
@@ -46,7 +59,7 @@ def test_read_multiple_files(neware_cycler):
     assert isinstance(unprocessed_dataframe, pl.DataFrame)
 
 
-def test_process_dataframe(monkeypatch):
+def test_process_dataframe():
     """Test the neware method."""
     mock_dataframe = pl.DataFrame(
         {
@@ -56,10 +69,18 @@ def test_process_dataframe(monkeypatch):
                 datetime(2022, 2, 2, 2, 2, 2),
                 datetime(2022, 2, 2, 2, 2, 3),
                 datetime(2022, 2, 2, 2, 2, 4),
+                datetime(2022, 2, 2, 2, 2, 5, 100000),
+            ],
+            "Total Time": [
+                datetime(2022, 2, 2, 2, 2, 0),
+                datetime(2022, 2, 2, 2, 2, 1),
+                datetime(2022, 2, 2, 2, 2, 2),
+                datetime(2022, 2, 2, 2, 2, 3),
+                datetime(2022, 2, 2, 2, 2, 4, 100000),
                 datetime(2022, 2, 2, 2, 2, 5),
             ],
             "Step Index": [1, 2, 1, 2, 4, 5],
-            "Current(mA)": [1, 2, 3, 4, 0, 0],
+            "Current(mA)": [1, 2, -3, -4, 0, 0],
             "Voltage(V)": [4, 5, 6, 7, 8, 9],
             "Chg. Cap.(mAh)": [
                 0,
@@ -69,15 +90,61 @@ def test_process_dataframe(monkeypatch):
                 0,
                 0,
             ],
-            "DChg. Cap.(mAh)": [0, 0, 10, 20, 20, 20],
+            "DChg. Cap.(mAh)": [0, 0, 10, 20, 0, 0],
+            "Capacity(mAh)": [0, 20, 10, 20, 0, 0],
             "T1(℃)": [25, 25, 25, 25, 25, 25],
         }
     )
     mock_dataframe.write_excel("tests/sample_data/mock_dataframe.xlsx")
     neware_cycler = Neware(input_data_path="tests/sample_data/mock_dataframe.xlsx")
+    pyprobe_dataframe = neware_cycler.pyprobe_dataframe
+
+    pyprobe_dataframe = pyprobe_dataframe.select(
+        [
+            "Time [s]",
+            "Step",
+            "Current [A]",
+            "Voltage [V]",
+            "Capacity [Ah]",
+            "Temperature [C]",
+        ]
+    )
+    expected_dataframe = pl.DataFrame(
+        {
+            "Time [s]": [0.0, 1.0, 2.0, 3.0, 4.0, 5.1],
+            "Step": [1, 2, 1, 2, 4, 5],
+            "Current [A]": [1e-3, 2e-3, -3e-3, -4e-3, 0, 0],
+            "Voltage [V]": [4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
+            "Capacity [Ah]": [20.0e-3, 40.0e-3, 30.0e-3, 20.0e-3, 20.0e-3, 20.0e-3],
+            "Temperature [C]": [25.0, 25.0, 25.0, 25.0, 25.0, 25.0],
+        }
+    )
+    pl_testing.assert_frame_equal(pyprobe_dataframe, expected_dataframe)
+    os.remove("tests/sample_data/mock_dataframe.xlsx")
 
+    # Test with a dataframe that does not contain a Charge or Discharge Capacity column
+    mock_dataframe = mock_dataframe.drop("Chg. Cap.(mAh)")
+    mock_dataframe = mock_dataframe.drop("DChg. Cap.(mAh)")
+    mock_dataframe.write_excel("tests/sample_data/mock_dataframe.xlsx")
+    neware_cycler = Neware(input_data_path="tests/sample_data/mock_dataframe.xlsx")
     pyprobe_dataframe = neware_cycler.pyprobe_dataframe
+    pyprobe_dataframe = pyprobe_dataframe.select(
+        [
+            "Time [s]",
+            "Step",
+            "Current [A]",
+            "Voltage [V]",
+            "Capacity [Ah]",
+            "Temperature [C]",
+        ]
+    )
+    pl_testing.assert_frame_equal(pyprobe_dataframe, expected_dataframe)
 
+    # Test with a dataframe that does not contain a "Date" column
+    mock_dataframe = mock_dataframe.drop("Date")
+    mock_dataframe.write_excel("tests/sample_data/mock_dataframe.xlsx")
+    neware_cycler = Neware(input_data_path="tests/sample_data/mock_dataframe.xlsx")
+    pyprobe_dataframe = neware_cycler.pyprobe_dataframe
     pyprobe_dataframe = pyprobe_dataframe.select(
         [
             "Time [s]",
@@ -90,9 +157,9 @@ def test_process_dataframe(monkeypatch):
     )
     expected_dataframe = pl.DataFrame(
         {
-            "Time [s]": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0],
+            "Time [s]": [0.0, 1.0, 2.0, 3.0, 4.1, 5.0],
             "Step": [1, 2, 1, 2, 4, 5],
-            "Current [A]": [1e-3, 2e-3, 3e-3, 4e-3, 0, 0],
+            "Current [A]": [1e-3, 2e-3, -3e-3, -4e-3, 0, 0],
             "Voltage [V]": [4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
             "Capacity [Ah]": [20.0e-3, 40.0e-3, 30.0e-3, 20.0e-3, 20.0e-3, 20.0e-3],
             "Temperature [C]": [25.0, 25.0, 25.0, 25.0, 25.0, 25.0],
diff --git a/tests/sample_data/arbin/sample_data_arbin.csv b/tests/sample_data/arbin/sample_data_arbin.csv
new file mode 100644
index 0000000..c8c4726
--- /dev/null
+++ b/tests/sample_data/arbin/sample_data_arbin.csv
@@ -0,0 +1,14 @@
+﻿Data Point,Date Time,Test Time (s),Step Time (s),Cycle Index,Step Index,TC_Counter1,TC_Counter2,TC_Counter3,Current (A),Voltage (V),Power (W),Charge Capacity (Ah),Discharge Capacity (Ah),Charge Energy (Wh),Discharge Energy (Wh),Capacity (Ah),mAh/g,ACR (Ohm),dV/dt (V/s),Internal Resistance (Ohm),dQ/dV (Ah/V),dV/dQ (V/Ah),Aux_Temperature_1 (C),Aux_dT/dt_1 (C/s)
+1,"	09/20/2024 08:32:34.558",30.0005,30.0005,1,1,0,0,0,0,3.534595,0,0,0,0,0,0,0,,1.98217E-05,,,,24.66422,-0.09131343
+2,"	09/20/2024 08:33:04.559",60.0008,60.0008,1,1,0,0,0,0,3.534597,0,0,0,0,0,0,0,,2.71962E-05,,,,24.73967,0.02648412
+3,"	09/20/2024 08:33:34.559",90.0013,90.0013,1,1,0,0,0,0,3.534578,0,0,0,0,0,0,0,,1.00484E-05,,,,24.71824,0.04604245
+4,"	09/20/2024 08:34:04.559",120.0016,120.0016,1,1,0,0,0,0,3.534572,0,0,0,0,0,0,0,,2.60919E-05,,,,24.63211,-0.09084845
+5,"	09/20/2024 08:34:34.560",150.0017,150.0017,1,1,0,0,0,0,3.534552,0,0,0,0,0,0,0,,1.1247E-05,,0,0,24.75344,0.06064539
+6,"	09/20/2024 08:35:04.560",180.0019,180.0019,1,1,0,0,0,0,3.534617,0,0,0,0,0,0,0,,9.40362E-06,,,,24.62239,-0.1132662
+7,"	09/20/2024 08:35:34.560",210.0021,210.0021,1,1,0,0,0,0,3.534601,0,0,0,0,0,0,0,,2.22192E-05,,,,24.75955,0.01128928
+8,"	09/20/2024 08:36:04.560",240.0022,240.0022,1,1,0,0,0,0,3.53458,0,0,0,0,0,0,0,,2.30485E-05,,,,24.721,0.0339758
+9,"	09/20/2024 08:36:34.560",270.0025,270.0025,1,1,0,0,0,0,3.534572,0,0,0,0,0,0,0,,2.02819E-05,,0,0,24.73034,0.07981738
+10,"	09/20/2024 08:37:04.559",300.0008,300.0008,1,1,0,0,0,0,3.534585,0,0,0,0,0,0,0,,2.2678E-05,0,0,0,24.72579,0.03765556
+11,"	09/20/2024 08:37:04.562",300.0039,0.0006,1,2,0,0,0,0,3.534586,0,0,0,0,0,0,0,,2.2678E-05,0,0,0,24.72550637,0.037089043
+12,"	09/20/2024 08:37:05.256",300.6979,0.0012,1,3,0,0,0,2.647604,3.594547,9.516937015,2.01404E-05,2.04379E-05,7.1302E-05,7.21224E-05,2.01404E-05,0,,0.119767502,,,,24.66201,-0.08973769
+13,"	09/20/2024 08:37:05.772",301.214,0.5173,1,3,0,0,0,2.650138,3.599601,9.539439395,0.000400839,2.04379E-05,0.001441009,7.21224E-05,0.000400839,0,,,,,,24.68785,-0.08973769
\ No newline at end of file
diff --git a/tests/sample_data/basytec/sample_data_basytec.txt b/tests/sample_data/basytec/sample_data_basytec.txt
new file mode 100644
index 0000000..49e4743
--- /dev/null
+++ b/tests/sample_data/basytec/sample_data_basytec.txt
@@ -0,0 +1,87 @@
+~Resultfile from Basytec Battery Test System
+~Date and Time of Data Converting: 07.07.2023 10:55:58
+~
+~Name of Test: test
+~Battery: cell
+~Testplan: plan.pln
+~Testchannel: 1814 CH14 XCTS_40
+~Start of Test: 19.06.2023 17:56:53
+~End of Test: 05.07.2023 11:05:04
+~Operator (Test): basytec
+~Operator (Data converting): basytec
+~
+~Time[s]	DataSet	t-Step[s]	t-Set[s]	t-Cyc[s]	Line	Command	U[V]	I[A]	P[W]	Ah[Ah]	Ah-Cyc-Charge	Ah-Cyc-Discharge	Ah-Cyc-Charge-0	Ah-Cyc-Discharge-0	Ah-Step	Wh[Wh]	Wh-Cyc-Charge	Wh-Cyc-Discharge	Wh-Cyc-Charge-0	Wh-Cyc-Discharge-0	Wh-Step	T1[�C]	R-AC	R-DC	Level	Cyc-Count	Count	State
+0	1	0	0	0	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	0	1	3
+0.00401299999999999	2	0.00401299999999999	0.00401299999999999	0.00401299999999999	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	0
+1.002823	3	1.002823	1.002823	1.002823	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+2.00232933333334	4	2.00232933333334	2.00232933333334	2.00232933333334	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+3.003276	5	3.003276	3.003276	3.003276	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+4.00280533333333	6	4.00280533333333	4.00280533333333	4.00280533333333	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47647	0	0	1	1	1	1
+5.002879	7	5.002879	5.002879	5.002879	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+6.00394733333334	8	6.00394733333334	6.00394733333334	6.00394733333334	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+7.002707	9	7.002707	7.002707	7.002707	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47647	0	0	1	1	1	1
+8.002436	10	8.002436	8.002436	8.002436	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+9.00285333333332	11	9.00285333333332	9.00285333333332	9.00285333333332	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47647	0	0	1	1	1	1
+10.0027263333333	12	10.0027263333333	10.0027263333333	10.0027263333333	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47647	0	0	1	1	1	1
+11.002709	13	11.002709	11.002709	11.002709	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+12.0024576666667	14	12.0024576666667	12.0024576666667	12.0024576666667	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47647	0	0	1	1	1	1
+13.0030616666667	15	13.0030616666667	13.0030616666667	13.0030616666667	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+14.0028443333333	16	14.0028443333333	14.0028443333333	14.0028443333333	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+15.0027586666667	17	15.0027586666667	15.0027586666667	15.0027586666667	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+16.0028896666667	18	16.0028896666667	16.0028896666667	16.0028896666667	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+17.003209	19	17.003209	17.003209	17.003209	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+18.0026233333333	20	18.0026233333333	18.0026233333333	18.0026233333333	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.48258	0	0	1	1	1	1
+19.0030663333333	21	19.0030663333333	19.0030663333333	19.0030663333333	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+20.002527	22	20.002527	20.002527	20.002527	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+21.0026856666667	23	21.0026856666667	21.0026856666667	21.0026856666667	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+22.0025676666667	24	22.0025676666667	22.0025676666667	22.0025676666667	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+23.0030373333333	25	23.0030373333333	23.0030373333333	23.0030373333333	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47647	0	0	1	1	1	1
+24.0026603333333	26	24.0026603333333	24.0026603333333	24.0026603333333	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47647	0	0	1	1	1	1
+25.0030303333333	27	25.0030303333333	25.0030303333333	25.0030303333333	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47647	0	0	1	1	1	1
+26.003809	28	26.003809	26.003809	26.003809	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+27.0026733333333	29	27.0026733333333	27.0026733333333	27.0026733333333	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+28.002601	30	28.002601	28.002601	28.002601	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+29.00298	31	29.00298	29.00298	29.00298	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+30.003332	32	30.003332	30.003332	30.003332	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47647	0	0	1	1	1	1
+31.002611	33	31.002611	31.002611	31.002611	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+32.0028283333333	34	32.0028283333333	32.0028283333333	32.0028283333333	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+33.0026066666667	35	33.0026066666667	33.0026066666667	33.0026066666667	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+34.002829	36	34.002829	34.002829	34.002829	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+35.0031953333334	37	35.0031953333334	35.0031953333334	35.0031953333334	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+36.0027136666667	38	36.0027136666667	36.0027136666667	36.0027136666667	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+37.0026203333332	39	37.0026203333332	37.0026203333332	37.0026203333332	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+38.002518	40	38.002518	38.002518	38.002518	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47647	0	0	1	1	1	1
+39.0025693333332	41	39.0025693333332	39.0025693333332	39.0025693333332	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47647	0	0	1	1	1	1
+40.0033496666668	42	40.0033496666668	40.0033496666668	40.0033496666668	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47647	0	0	1	1	1	1
+41.0029476666667	43	41.0029476666667	41.0029476666667	41.0029476666667	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+42.0031113333332	44	42.0031113333332	42.0031113333332	42.0031113333332	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+43.0028363333333	45	43.0028363333333	43.0028363333333	43.0028363333333	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+44.0032296666668	46	44.0032296666668	44.0032296666668	44.0032296666668	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+45.0027653333332	47	45.0027653333332	45.0027653333332	45.0027653333332	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+46.0025643333334	48	46.0025643333334	46.0025643333334	46.0025643333334	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+47.002801	49	47.002801	47.002801	47.002801	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+48.0028053333334	50	48.0028053333334	48.0028053333334	48.0028053333334	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+49.0030513333333	51	49.0030513333333	49.0030513333333	49.0030513333333	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+50.0032166666666	52	50.0032166666666	50.0032166666666	50.0032166666666	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+51.0028750000001	53	51.0028750000001	51.0028750000001	51.0028750000001	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.48258	0	0	1	1	1	1
+52.0029506666668	54	52.0029506666668	52.0029506666668	52.0029506666668	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+53.0026653333334	55	53.0026653333334	53.0026653333334	53.0026653333334	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+54.0035790000001	56	54.0035790000001	54.0035790000001	54.0035790000001	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+55.0025433333335	57	55.0025433333335	55.0025433333335	55.0025433333335	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+56.0029926666668	58	56.0029926666668	56.0029926666668	56.0029926666668	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+57.0006923333335	59	57.0006923333335	57.0006923333335	57.0006923333335	3	Pause	3.52575489148741	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+58.0032793333332	60	58.0032793333332	58.0032793333332	58.0032793333332	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+59.0035526666665	61	59.0035526666665	59.0035526666665	59.0035526666665	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	1
+59.9999993333334	62	59.9999993333334	59.9999993333334	59.9999993333334	3	Pause	3.5255631284671	0	0	0	0	0	0	0	0	0	0	0	0	0	0	25.47953	0	0	1	1	1	2
+60.2322676666668	63	0	60.2322676666668	60.2322676666668	4	Charge	3.52575489148741	0.00195196122033401	0.00688213682058638	1.259385498273E-7	1.259385498273E-7	0	1.259385498273E-7	0	1.259385498273E-7	4.44028458080434E-7	4.44028458080434E-7	0	4.44028458080434E-7	0	4.44028458080434E-7	25.47953	0	0	1	1	1	3
+60.2382183333332	64	0.00595066666666666	60.2382183333332	60.2382183333332	4	Charge	3.52997367793431	0.453505656857602	1.60086303150165	8.75566604107107E-7	8.75566604107107E-7	0	8.75566604107107E-7	0	8.75566604107107E-7	3.09019575792926E-6	3.09019575792926E-6	0	3.09019575792926E-6	0	3.09019575792926E-6	25.47953	0.00934282342688728	0.00934282342688728	1	1	1	0
+61.2359273333333	65	1.00365966666667	61.2359273333333	61.2359273333333	4	Charge	3.53074073001556	0.449601734416934	1.58742715599151	0.000125506197586236	0.000125506197586236	0	0.000125506197586236	0	0.000125506197586236	0.00044309987534853	0.00044309987534853	0	0.00044309987534853	0	0.00044309987534853	25.47953	0.00934282342688728	0.0111378108693745	1	1	1	1
+62.2359889999999	66	2.00372133333333	62.2359889999999	62.2359889999999	4	Charge	3.53112425605619	0.448951080676823	1.58530205076057	0.000250358011318813	0.000250358011318813	0	0.000250358011318813	0	0.000250358011318813	0.000883949623025544	0.000883949623025544	0	0.000883949623025544	0	0.000883949623025544	25.48258	0.00934282342688728	0.0120120249192036	1	1	1	1
+63.2355006666668	67	3.003233	63.2355006666668	63.2355006666668	4	Charge	3.53150778209682	0.448951080676823	1.58547423519098	0.00037510288607274	0.00037510288607274	0	0.00037510288607274	0	0.00037510288607274	0.00132445862000565	0.00132445862000565	0	0.00132445862000565	0	0.00132445862000565	25.48258	0.00934282342688728	0.0128700267138511	1	1	1	1
+64.2362143333334	68	4.00394666666668	64.2362143333334	64.2362143333334	4	Charge	3.53169954511713	0.449601734416934	1.58785824092416	0.000500030166903541	0.000500030166903541	0	0.000500030166903541	0	0.000500030166903541	0.00176564619027433	0.00176564619027433	0	0.00176564619027433	0	0.00176564619027433	25.48258	0.00934282342688728	0.0132796975750234	1	1	1	1
+65.2362810000001	69	5.00401333333332	65.2362810000001	65.2362810000001	4	Charge	3.53189130813745	0.448951080676823	1.58564641962138	0.000624815120301225	0.000624815120301225	0	0.000624815120301225	0	0.000624815120301225	0.00220635871205227	0.00220635871205227	0	0.00220635871205227	0	0.00220635871205227	25.48258	0.00934282342688728	0.013728028439878	1	1	1	1
+66.2359013333334	70	6.00363366666667	66.2359013333334	66.2359013333334	4	Charge	3.53208307115776	0.448951080676823	1.58573251183659	0.000749617238366292	0.000749617238366292	0	0.000749617238366292	0	0.000749617238366292	0.00264716195585662	0.00264716195585662	0	0.00264716195585662	0	0.00264716195585662	25.47953	0.00934282342688728	0.0141570293372017	1	1	1	1
+67.2357786666667	71	7.00351099999999	67.2357786666667	67.2357786666667	4	Charge	3.53227483417807	0.449601734416934	1.58811689188375	0.000874462050401922	0.000874462050401922	0	0.000874462050401922	0	0.000874462050401922	0.00308814490084926	0.00308814490084926	0	0.00308814490084926	0	0.00308814490084926	25.47953	0.00934282342688728	0.0145648295984128	1	1	1	1
+68.2355886666667	72	8.00332099999999	68.2355886666667	68.2355886666667	4	Charge	3.53246659719839	0.448951080676823	1.585904696267	0.000999238021103003	0.000999238021103003	0	0.000999238021103003	0	0.000999238021103003	0.00352890651280298	0.00352890651280298	0	0.00352890651280298	0	0.00352890651280298	25.47953	0.00934282342688728	0.0150150311318493	1	1	1	1
+69.2353230000001	73	9.00305533333332	69.2353230000001	69.2353230000001	4	Charge	3.5326583602187	0.448951080676823	1.5859907884822	0.00112403671203023	0.00112403671203023	0	0.00112403671203023	0	0.00112403671203023	0.0039697725279783	0.0039697725279783	0	0.0039697725279783	0	0.0039697725279783	25.47953	0.00934282342688728	0.015444032029173	1	1	1	1
+70.2358036666668	74	10.003536	70.2358036666668	70.2358036666668	4	Charge	3.53285012323902	0.449601734416934	1.58837554284334	0.001248916998009	0.001248916998009	0	0.001248916998009	0	0.001248916998009	0.00441094744918941	0.00441094744918941	0	0.00441094744918941	0	0.00441094744918941	25.47953	0.00934282342688728	0.0158499616218021	1	1	1	1
\ No newline at end of file
diff --git a/tests/sample_data/maccor/sample_data_maccor.csv b/tests/sample_data/maccor/sample_data_maccor.csv
new file mode 100644
index 0000000..e9c06eb
--- /dev/null
+++ b/tests/sample_data/maccor/sample_data_maccor.csv
@@ -0,0 +1,18 @@
+Today's Date ,28-Nov-23
+Date of Test:,23-Nov-23 3:56:08 PM
+Rec,Cycle C,Step,Test Time (sec),Step Time (sec),Capacity,Energy,Current,Voltage,DPT Time,Temp 1
+1,1,1,0.0000,0.0000,0,0,0,3.668,23-Nov-23 3:56:11 PM,22.2591
+2,1,1,1.0000,1.0000,0,0,0,3.668,23-Nov-23 3:56:12 PM,22.2591
+3,1,1,2.0000,2.0000,0,0,0,3.668,23-Nov-23 3:56:13 PM,22.2591
+4,1,1,3.0000,3.0000,0,0,0,3.668,23-Nov-23 3:56:14 PM,22.2591
+5,1,1,4.0000,4.0000,0,0,0,3.668,23-Nov-23 3:56:15 PM,22.2591
+6,1,1,5.0000,5.0000,0,0,0,3.668,23-Nov-23 3:56:16 PM,22.2591
+7,1,1,6.0000,6.0000,0,0,0,3.668,23-Nov-23 3:56:17 PM,22.2591
+8,1,1,7.0000,7.0000,0,0,0,3.668,23-Nov-23 3:56:18 PM,22.2591
+9,1,1,8.0000,8.0000,0,0,0,3.668,23-Nov-23 3:56:19 PM,22.2591
+10,1,1,9.0000,9.0000,0,0,0,3.668,23-Nov-23 3:56:20 PM,22.2591
+11,1,1,10.0000,10.0000,0,0,0,3.668,23-Nov-23 3:56:21 PM,22.2591
+12,1,2,10.0600,0.0600,0.000,0.002,28.844,3.709,23-Nov-23 3:56:21 PM,22.2591
+13,1,2,11.0600,1.0600,0.008,0.031,28.799,3.713,23-Nov-23 3:56:22 PM,22.2591
+14,1,2,12.0600,2.0600,0.016,0.061,28.798,3.715,23-Nov-23 3:56:23 PM,22.2591
+15,1,2,13.0600,3.0600,0.024,0.091,28.798,3.716,23-Nov-23 3:56:24 PM,22.2591
\ No newline at end of file