Repository: pandas-dev/pandas
File: pandas/io/sas/sas7bdat.py (Python)

Code before:
        FilePath,
        ReadBuffer,
    )


_unix_origin = Timestamp("1970-01-01")
_sas_origin = Timestamp("1960-01-01")


def _convert_datetimes(sas_datetimes: pd.Series, unit: str) -> pd.Series:
    """
    Convert to Timestamp if possible, otherwise to datetime.datetime.
    SAS float64 lacks precision for more than ms resolution so the fit
    to datetime.datetime is ok.

    Parameters
    ----------
    sas_datetimes : {Series, Sequence[float]}
       Dates or datetimes in SAS
    unit : {'d', 's'}
       "d" if the floats represent dates, "s" for datetimes

    Returns
    -------
    Series
       Series of datetime64 dtype or datetime.datetime.
    """
    td = (_sas_origin - _unix_origin).as_unit("s")
    if unit == "s":
        millis = cast_from_unit_vectorized(
            sas_datetimes._values, unit="s", out_unit="ms"
        )
        dt64ms = millis.view("M8[ms]") + td
        return pd.Series(dt64ms, index=sas_datetimes.index, copy=False)
    else:
        vals = np.array(sas_datetimes, dtype="M8[D]") + td
        return pd.Series(vals, dtype="M8[s]", index=sas_datetimes.index, copy=False)


class _Column:
    col_id: int
    name: str | bytes
    label: str | bytes
    format: str | bytes
    ctype: bytes
    length: int

    def __init__(
        self,
        col_id: int,
        # These can be bytes when convert_header_text is False

Review comment (refactor):
nit: Not sure if storing the thresholds in a numpy array here is doing much since these values are just iterated over later below e.g just could simplify below to

```python
for threshold in [_SAS_MARCH1_4000, _SAS_MARCH1_8000]:
    correction[valid] += (values[valid] >= np.int64(threshold * scale)).astype(np.float64) * scale
```

Code after:
        FilePath,
        ReadBuffer,
    )


_unix_origin = Timestamp("1970-01-01")
_sas_origin = Timestamp("1960-01-01")

# SAS uses a modified Gregorian calendar where years divisible by 4000 are
# not leap years (unlike proleptic Gregorian). These are the SAS day counts
# for the first day affected by each 4000-year boundary.
# See https://communities.sas.com/t5/SAS-Programming/Leap-Years-divisible-by-4000/td-p/663467
_SAS_MARCH1_4000 = 745154  # SAS day count for March 1, 4000
_SAS_MARCH1_8000 = 2206123  # SAS day count for March 1, 8000


def _sas_to_gregorian_correction(values: np.ndarray, unit: str) -> np.ndarray:
    """
    Compute the additive correction (in `unit`) to convert SAS day/second counts
    to proleptic Gregorian day/second counts.

    SAS omits Feb 29 for years divisible by 4000 (unlike proleptic Gregorian);
    this adds back the missing days. `unit` must be "d" (days) or "s" (seconds).
    """
    scale = 86400 if unit == "s" else 1
    thresholds = np.array([_SAS_MARCH1_4000, _SAS_MARCH1_8000], dtype=np.int64) * scale
    correction = np.zeros(len(values), dtype=np.float64)
    valid = ~np.isnan(values)
    for threshold in thresholds:
        correction[valid] += (values[valid] >= threshold).astype(np.float64) * scale
    return correction


def _convert_datetimes(sas_datetimes: pd.Series, unit: str) -> pd.Series:
    """
    Convert to Timestamp if possible, otherwise to datetime.datetime.
    SAS float64 lacks precision for more than ms resolution so the fit
    to datetime.datetime is ok.

    Parameters
    ----------
    sas_datetimes : {Series, Sequence[float]}
       Dates or datetimes in SAS
    unit : {'d', 's'}
       "d" if the floats represent dates, "s" for datetimes

    Returns
    -------
    Series
       Series of datetime64 dtype or datetime.datetime.
    """