Repository: pvlib/pvlib-python
Pull request #942 (2020-03-23 16:24:26)

Spa.julian day microsecond fix 
 - [X] I am familiar with the [contributing guidelines](https://pvlib-python.readthedocs.io/en/latest/contributing.html)
 - [X] Tests added
 - [X] Adds description and name entries in the appropriate "what's new" file in [`docs/sphinx/source/whatsnew`](https://github.com/pvlib/pvlib-python/tree/master/docs/sphinx/source/whatsnew) for all changes. Includes link to the GitHub Issue with `` :issue:`num` `` or this Pull Request with `` :pull:`num` ``. Includes contributor name and/or GitHub username (link with `` :ghuser:`user` ``).
 - [X] New code is fully documented. Includes [numpydoc](https://numpydoc.readthedocs.io/en/latest/format.html) compliant docstrings, examples, and comments where necessary.
 - [X] Pull request is nearly complete and ready for detailed review.
 - [x] Maintainer: Appropriate GitHub Labels and Milestone are assigned to the Pull Request and linked Issue.

<!-- Brief description of the problem and proposed solution (if not already fully described in the issue linked to above): -->



Patch:
diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst
index defe3e9..13b8c6f 100644
--- a/docs/sphinx/source/api.rst
+++ b/docs/sphinx/source/api.rst
@@ -308,25 +308,33 @@ Functions for fitting diode models
     ivtools.fit_sdm_cec_sam
     ivtools.fit_sdm_desoto
 
-Losses
-------
+Other
+-----
 
 .. autosummary::
    :toctree: generated/
 
-   losses.soiling_hsu
-   losses.soiling_kimber
+   pvsystem.retrieve_sam
+   pvsystem.systemdef
+   pvsystem.scale_voltage_current_power
 
 
-Other
------
+Effects on PV System Output
+===========================
 
 .. autosummary::
    :toctree: generated/
 
-   pvsystem.retrieve_sam
-   pvsystem.systemdef
-   pvsystem.scale_voltage_current_power
+   snow.coverage_nrel
+   snow.fully_covered_nrel
+   snow.dc_loss_nrel
+
+.. autosummary::
+   :toctree: generated/
+
+   losses.soiling_hsu
+   losses.soiling_kimber
+
 
 
 Tracking
@@ -576,4 +584,4 @@ Methods for manipulating irradiance for temporal or spatial considerations
 .. autosummary::
    :toctree: generated/
 
-   scaling.wvm
\ No newline at end of file
+   scaling.wvm
diff --git a/docs/sphinx/source/whatsnew/v0.7.2.rst b/docs/sphinx/source/whatsnew/v0.7.2.rst
index 2eda805..41bec86 100644
--- a/docs/sphinx/source/whatsnew/v0.7.2.rst
+++ b/docs/sphinx/source/whatsnew/v0.7.2.rst
@@ -22,6 +22,9 @@ Enhancements
 * Add Kimber soiling model :py:func:`pvlib.losses.soiling_kimber`. (:pull:`860`)
 * Add :func:`~pvlib.iotools.read_pvgis_tmy` for files downloaded using the
   PVGIS tool. (:issue:`880`)
+* Add new module :py:mod:`pvlib.snow` to contain models related to snow coverage and effects on a PV system. (:pull:`764`)
+* Add snow coverage model :py:func:`pvlib.snow.coverage_nrel` and function to identify when modules are fully covered by snow :py:func:`pvlib.snow.fully_covered_nrel`. (:issue:`577`)
+* Add function :py:func:`pvlib.snow.dc_loss_nrel` for effect of snow coverage on DC output. (:pull:`764`)
 
 Bug fixes
 ~~~~~~~~~
@@ -45,6 +48,8 @@ Bug fixes
   and various test functions.
 * Fix :py:func:`~pvlib.iotools.read_tmy3` so that when coerced to a single year
   the TMY3 index will be monotonically increasing. (:pull:`910`)
+* Fix :py:func:`pvlib.spa.julian_day_dt` so that microseconds are scaled
+  correctly (:issue:`940`) (:pull:`942`)
 
 Testing
 ~~~~~~~
@@ -83,3 +88,4 @@ Contributors
 * Karthikeyan Singaravelan (:ghuser:`tirkarthi`)
 * Siyan (Veronica) Guo (:ghuser:`veronicaguo`)
 * Eric Fitch (:ghuser:`ericf900`)
+* Joseph Palakapilly (:ghuser:`JPalakapilly`)
diff --git a/pvlib/__init__.py b/pvlib/__init__.py
index 098202c..df2b3c5 100644
--- a/pvlib/__init__.py
+++ b/pvlib/__init__.py
@@ -15,3 +15,4 @@ from pvlib import modelchain  # noqa: F401
 from pvlib import singlediode  # noqa: F401
 from pvlib import bifacial  # noqa: F401
 from pvlib import losses  # noqa: F401
+from pvlib import snow  # noqa: F401
diff --git a/pvlib/clearsky.py b/pvlib/clearsky.py
index 6b5df95..a6794e2 100644
--- a/pvlib/clearsky.py
+++ b/pvlib/clearsky.py
@@ -855,7 +855,7 @@ def bird(zenith, airmass_relative, aod380, aod500, precipitable_water,
     See also
     --------
     pvlib.atmosphere.bird_hulstrom80_aod_bb
-    pvlib.atmosphere.relativeairmass
+    pvlib.atmosphere.get_relative_airmass
 
     References
     ----------
diff --git a/pvlib/iam.py b/pvlib/iam.py
index f6c1b54..6143439 100644
--- a/pvlib/iam.py
+++ b/pvlib/iam.py
@@ -73,9 +73,9 @@ def ashrae(aoi, b=0.05):
 
     See Also
     --------
-    iam.physical
-    iam.martin_ruiz
-    iam.interp
+    pvlib.iam.physical
+    pvlib.iam.martin_ruiz
+    pvlib.iam.interp
     """
 
     iam = 1 - b * ((1 / np.cos(np.radians(aoi)) - 1))
@@ -143,10 +143,10 @@ def physical(aoi, n=1.526, K=4., L=0.002):
 
     See Also
     --------
-    iam.martin_ruiz
-    iam.ashrae
-    iam.interp
-    iam.sapm
+    pvlib.iam.martin_ruiz
+    pvlib.iam.ashrae
+    pvlib.iam.interp
+    pvlib.iam.sapm
     """
     zeroang = 1e-06
 
@@ -248,11 +248,11 @@ def martin_ruiz(aoi, a_r=0.16):
 
     See Also
     --------
-    iam.martin_ruiz_diffuse
-    iam.physical
-    iam.ashrae
-    iam.interp
-    iam.sapm
+    pvlib.iam.martin_ruiz_diffuse
+    pvlib.iam.physical
+    pvlib.iam.ashrae
+    pvlib.iam.interp
+    pvlib.iam.sapm
     '''
     # Contributed by Anton Driesse (@adriesse), PV Performance Labs. July, 2019
 
@@ -334,11 +334,11 @@ def martin_ruiz_diffuse(surface_tilt, a_r=0.16, c1=0.4244, c2=None):
 
     See Also
     --------
-    iam.martin_ruiz
-    iam.physical
-    iam.ashrae
-    iam.interp
-    iam.sapm
+    pvlib.iam.martin_ruiz
+    pvlib.iam.physical
+    pvlib.iam.ashrae
+    pvlib.iam.interp
+    pvlib.iam.sapm
     '''
     # Contributed by Anton Driesse (@adriesse), PV Performance Labs. Oct. 2019
 
@@ -424,10 +424,10 @@ def interp(aoi, theta_ref, iam_ref, method='linear', normalize=True):
 
     See Also
     --------
-    iam.physical
-    iam.ashrae
-    iam.martin_ruiz
-    iam.sapm
+    pvlib.iam.physical
+    pvlib.iam.ashrae
+    pvlib.iam.martin_ruiz
+    pvlib.iam.sapm
     '''
     # Contributed by Anton Driesse (@adriesse), PV Performance Labs. July, 2019
 
@@ -507,10 +507,10 @@ def sapm(aoi, module, upper=None):
 
     See Also
     --------
-    iam.physical
-    iam.ashrae
-    iam.martin_ruiz
-    iam.interp
+    pvlib.iam.physical
+    pvlib.iam.ashrae
+    pvlib.iam.martin_ruiz
+    pvlib.iam.interp
     """
 
     aoi_coeff = [module['B5'], module['B4'], module['B3'], module['B2'],
diff --git a/pvlib/location.py b/pvlib/location.py
index afca46e..1298c93 100644
--- a/pvlib/location.py
+++ b/pvlib/location.py
@@ -54,7 +54,7 @@ class Location(object):
 
     See also
     --------
-    pvsystem.PVSystem
+    pvlib.pvsystem.PVSystem
     """
 
     def __init__(self, latitude, longitude, tz='UTC', altitude=0,
diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py
index 9b75f10..e4a3508 100644
--- a/pvlib/pvsystem.py
+++ b/pvlib/pvsystem.py
@@ -955,8 +955,8 @@ def systemdef(meta, surface_tilt, surface_azimuth, albedo, modules_per_string,
 
     See also
     --------
-    pvlib.tmy.readtmy3
-    pvlib.tmy.readtmy2
+    pvlib.iotools.read_tmy3
+    pvlib.iotools.read_tmy2
     '''
 
     try:
@@ -1689,8 +1689,8 @@ def sapm(effective_irradiance, temp_cell, module):
     See Also
     --------
     retrieve_sam
-    temperature.sapm_cell
-    temperature.sapm_module
+    pvlib.temperature.sapm_cell
+    pvlib.temperature.sapm_module
     '''
 
     # TODO: someday, change temp_ref and irrad_ref to reference_temperature and
diff --git a/pvlib/snow.py b/pvlib/snow.py
new file mode 100644
index 0000000..bf40c7b
--- /dev/null
+++ b/pvlib/snow.py
@@ -0,0 +1,187 @@
+"""
+The ``snow`` module contains functions that model module snow cover and the
+associated effects on PV module output
+"""
+
+import numpy as np
+import pandas as pd
+from pvlib.tools import sind
+
+
+def _time_delta_in_hours(times):
+    delta = times.to_series().diff()
+    return delta.dt.total_seconds().div(3600)
+
+
+def fully_covered_nrel(snowfall, threshold_snowfall=1.):
+    '''
+    Calculates the timesteps when the row's slant height is fully covered
+    by snow.
+
+    Parameters
+    ----------
+    snowfall : Series
+        Accumulated snowfall in each time period [cm]
+
+    threshold_snowfall : float, default 1.0
+        Hourly snowfall above which snow coverage is set to the row's slant
+        height. [cm/hr]
+
+    Returns
+    ----------
+    boolean: Series
+        True where the snowfall exceeds the defined threshold to fully cover
+        the panel.
+
+    Notes
+    -----
+    Implements the model described in [1]_ with minor improvements in [2]_.
+
+    References
+    ----------
+    .. [1] Marion, B.; Schaefer, R.; Caine, H.; Sanchez, G. (2013).
+       "Measured and modeled photovoltaic system energy losses from snow for
+       Colorado and Wisconsin locations." Solar Energy 97; pp.112-121.
+    .. [2] Ryberg, D; Freeman, J. "Integration, Validation, and Application
+       of a PV Snow Coverage Model in SAM" (2017) NREL Technical Report
+       NREL/TP-6A20-68705
+    '''
+    timestep = _time_delta_in_hours(snowfall.index)
+    hourly_snow_rate = snowfall / timestep
+    # if we can infer a time frequency, use first snowfall value
+    # otherwise the first snowfall value is ignored
+    freq = pd.infer_freq(snowfall.index)
+    if freq is not None:
+        timedelta = pd.tseries.frequencies.to_offset(freq) / pd.Timedelta('1h')
+        hourly_snow_rate.iloc[0] = snowfall[0] / timedelta
+    else:  # can't infer frequency from index
+        hourly_snow_rate[0] = 0  # replaces NaN
+    return hourly_snow_rate > threshold_snowfall
+
+
+def coverage_nrel(snowfall, poa_irradiance, temp_air, surface_tilt,
+                  initial_coverage=0, threshold_snowfall=1.,
+                  can_slide_coefficient=-80., slide_amount_coefficient=0.197):
+    '''
+    Calculates the fraction of the slant height of a row of modules covered by
+    snow at every time step.
+
+    Implements the model described in [1]_ with minor improvements in [2]_,
+    with the change that the output is in fraction of the row's slant height
+    rather than in tenths of the row slant height. As described in [1]_, model
+    validation focused on fixed tilt systems.
+
+    Parameters
+    ----------
+    snowfall : Series
+        Accumulated snowfall within each time period. [cm]
+    poa_irradiance : Series
+        Total in-plane irradiance [W/m^2]
+    temp_air : Series
+        Ambient air temperature [C]
+    surface_tilt : numeric
+        Tilt of module's from horizontal, e.g. surface facing up = 0,
+        surface facing horizon = 90. [degrees]
+    initial_coverage : float, default 0
+        Fraction of row's slant height that is covered with snow at the
+        beginning of the simulation. [unitless]
+    threshold_snowfall : float, default 1.0
+        Hourly snowfall above which snow coverage is set to the row's slant
+        height. [cm/hr]
+    can_slide_coefficient : float, default -80.
+        Coefficient to determine if snow can slide given irradiance and air
+        temperature. [W/(m^2 C)]
+    slide_amount_coefficient : float, default 0.197
+        Coefficient to determine fraction of snow that slides off in one hour.
+        [unitless]
+
+    Returns
+    -------
+    snow_coverage : Series
+        The fraction of the slant height of a row of modules that is covered
+        by snow at each time step.
+
+    Notes
+    -----
+    In [1]_, `can_slide_coefficient` is termed `m`, and the value of
+    `slide_amount_coefficient` is given in tenths of a module's slant height.
+
+    References
+    ----------
+    .. [1] Marion, B.; Schaefer, R.; Caine, H.; Sanchez, G. (2013).
+       "Measured and modeled photovoltaic system energy losses from snow for
+       Colorado and Wisconsin locations." Solar Energy 97; pp.112-121.
+    .. [2] Ryberg, D; Freeman, J. (2017). "Integration, Validation, and
+       Application of a PV Snow Coverage Model in SAM" NREL Technical Report
+       NREL/TP-6A20-68705
+    '''
+
+    # find times with new snowfall
+    new_snowfall = fully_covered_nrel(snowfall, threshold_snowfall)
+
+    # set up output Series
+    snow_coverage = pd.Series(np.nan, index=poa_irradiance.index)
+
+    # determine amount that snow can slide in each timestep
+    can_slide = temp_air > poa_irradiance / can_slide_coefficient
+    slide_amt = slide_amount_coefficient * sind(surface_tilt) * \
+        _time_delta_in_hours(poa_irradiance.index)
+    slide_amt[~can_slide] = 0.
+    # don't slide during snow events
+    slide_amt[new_snowfall] = 0.
+    # don't slide in the interval preceding the snowfall data
+    slide_amt.iloc[0] = 0
+
+    # build time series of cumulative slide amounts
+    sliding_period_ID = new_snowfall.cumsum()
+    cumulative_sliding = slide_amt.groupby(sliding_period_ID).cumsum()
+
+    # set up time series of snow coverage without any sliding applied
+    snow_coverage[new_snowfall] = 1.0
+    if np.isnan(snow_coverage.iloc[0]):
+        snow_coverage.iloc[0] = initial_coverage
+    snow_coverage.ffill(inplace=True)
+    snow_coverage -= cumulative_sliding
+
+    # clean up periods where row is completely uncovered
+    return snow_coverage.clip(lower=0)
+
+
+def dc_loss_nrel(snow_coverage, num_strings):
+    '''
+    Calculates the fraction of DC capacity lost due to snow coverage.
+
+    DC capacity loss assumes that if a string is partially covered by snow,
+    the string's capacity is lost; see [1]_, Eq. 11.8.
+
+    Module orientation is accounted for by specifying the number of cell
+    strings in parallel along the slant height.
+    For example, a typical 60-cell module has 3 parallel strings, each
+    comprising 20 cells in series, with the cells arranged in 6 columns of 10
+    cells each. For a row consisting of single modules, if the module is
+    mounted in portrait orientation, i.e., the row slant height is along a
+    column of 10 cells, there is 1 string in parallel along the row slant
+    height, so `num_strings=1`. In contrast, if the module is mounted in
+    landscape orientation with the row slant height comprising 6 cells, there
+    are 3 parallel strings along the row slant height, so `num_strings=3`.
+
+    Parameters
+    ----------
+    snow_coverage : numeric
+        The fraction of row slant height covered by snow at each time step.
+
+    num_strings: int
+        The number of parallel-connected strings along a row slant height.
+
+    Returns
+    -------
+    loss : numeric
+        fraction of DC capacity loss due to snow coverage at each time step.
+
+    References
+    ----------
+    .. [1] Gilman, P. et al., (2018). "SAM Photovoltaic Model Technical
+       Reference Update", NREL Technical Report NREL/TP-6A20-67399.
+       Available at https://www.nrel.gov/docs/fy18osti/67399.pdf
+    '''
+    return np.ceil(snow_coverage * num_strings) / num_strings
diff --git a/pvlib/solarposition.py b/pvlib/solarposition.py
index 402e28d..fbd7b9b 100644
--- a/pvlib/solarposition.py
+++ b/pvlib/solarposition.py
@@ -1126,7 +1126,7 @@ def equation_of_time_pvcdrom(dayofyear):
 
     See Also
     --------
-    equation_of_time_Spencer71
+    equation_of_time_spencer71
     """
     # day angle relative to Vernal Equinox, typically March 22 (day number 81)
     bday = \
@@ -1380,7 +1380,7 @@ def hour_angle(times, longitude, equation_of_time):
 
     See Also
     --------
-    equation_of_time_Spencer71
+    equation_of_time_spencer71
     equation_of_time_pvcdrom
     """
     naive_times = times.tz_localize(None)  # naive but still localized
diff --git a/pvlib/spa.py b/pvlib/spa.py
index 6c99a03..743df5c 100644
--- a/pvlib/spa.py
+++ b/pvlib/spa.py
@@ -430,7 +430,7 @@ def julian_day_dt(year, month, day, hour, minute, second, microsecond):
         month = month+12
     a = int(year/100)
     b = 2 - a + int(a * 0.25)
-    frac_of_day = (microsecond + (second + minute * 60 + hour * 3600)
+    frac_of_day = (microsecond / 1e6 + (second + minute * 60 + hour * 3600)
                    ) * 1.0 / (3600*24)
     d = day + frac_of_day
     jd = (int(365.25 * (year + 4716)) + int(30.6001 * (month + 1)) + d +


Test patch:
diff --git a/pvlib/tests/test_snow.py b/pvlib/tests/test_snow.py
new file mode 100644
index 0000000..c5ba88a
--- /dev/null
+++ b/pvlib/tests/test_snow.py
@@ -0,0 +1,97 @@
+import numpy as np
+import pandas as pd
+
+from pandas.util.testing import assert_series_equal
+
+from pvlib import snow
+from pvlib.tools import sind
+
+
+def test_fully_covered_nrel():
+    dt = pd.date_range(start="2019-1-1 12:00:00", end="2019-1-1 18:00:00",
+                       freq='1h')
+    snowfall_data = pd.Series([1, 5, .6, 4, .23, -5, 19], index=dt)
+    expected = pd.Series([False, True, False, True, False, False, True],
+                         index=dt)
+    fully_covered = snow.fully_covered_nrel(snowfall_data)
+    assert_series_equal(expected, fully_covered)
+
+
+def test_coverage_nrel_hourly():
+    surface_tilt = 45
+    slide_amount_coefficient = 0.197
+    dt = pd.date_range(start="2019-1-1 10:00:00", end="2019-1-1 17:00:00",
+                       freq='1h')
+    poa_irradiance = pd.Series([400, 200, 100, 1234, 134, 982, 100, 100],
+                               index=dt)
+    temp_air = pd.Series([10, 2, 10, 1234, 34, 982, 10, 10], index=dt)
+    snowfall_data = pd.Series([1, .5, .6, .4, .23, -5, .1, .1], index=dt)
+    snow_coverage = snow.coverage_nrel(
+        snowfall_data, poa_irradiance, temp_air, surface_tilt,
+        threshold_snowfall=0.6)
+
+    slide_amt = slide_amount_coefficient * sind(surface_tilt)
+    covered = 1.0 - slide_amt * np.array([0, 1, 2, 3, 4, 5, 6, 7])
+    expected = pd.Series(covered, index=dt)
+    assert_series_equal(expected, snow_coverage)
+
+
+def test_coverage_nrel_subhourly():
+    surface_tilt = 45
+    slide_amount_coefficient = 0.197
+    dt = pd.date_range(start="2019-1-1 11:00:00", end="2019-1-1 14:00:00",
+                       freq='15T')
+    poa_irradiance = pd.Series([400, 200, 100, 1234, 134, 982, 100, 100, 100,
+                                100, 100, 100, 0],
+                               index=dt)
+    temp_air = pd.Series([10, 2, 10, 1234, 34, 982, 10, 10, 10, 10, -10, -10,
+                          10], index=dt)
+    snowfall_data = pd.Series([1, .5, .6, .4, .23, -5, .1, .1, 0., 1., 0., 0.,
+                               0.], index=dt)
+    snow_coverage = snow.coverage_nrel(
+        snowfall_data, poa_irradiance, temp_air, surface_tilt)
+    slide_amt = slide_amount_coefficient * sind(surface_tilt) * 0.25
+    covered = np.append(np.array([1., 1., 1., 1.]),
+                        1.0 - slide_amt * np.array([1, 2, 3, 4, 5]))
+    covered = np.append(covered, np.array([1., 1., 1., 1. - slide_amt]))
+    expected = pd.Series(covered, index=dt)
+    assert_series_equal(expected, snow_coverage)
+
+
+def test_fully_covered_nrel_irregular():
+    # test when frequency is not specified and can't be inferred
+    dt = pd.DatetimeIndex(["2019-1-1 11:00:00", "2019-1-1 14:30:00",
+                           "2019-1-1 15:07:00", "2019-1-1 14:00:00"])
+    snowfall_data = pd.Series([1, .5, .6, .4], index=dt)
+    snow_coverage = snow.fully_covered_nrel(snowfall_data,
+                                            threshold_snowfall=0.5)
+    covered = np.array([False, False, True, False])
+    expected = pd.Series(covered, index=dt)
+    assert_series_equal(expected, snow_coverage)
+
+
+def test_coverage_nrel_initial():
+    surface_tilt = 45
+    slide_amount_coefficient = 0.197
+    dt = pd.date_range(start="2019-1-1 10:00:00", end="2019-1-1 17:00:00",
+                       freq='1h')
+    poa_irradiance = pd.Series([400, 200, 100, 1234, 134, 982, 100, 100],
+                               index=dt)
+    temp_air = pd.Series([10, 2, 10, 1234, 34, 982, 10, 10], index=dt)
+    snowfall_data = pd.Series([0, .5, .6, .4, .23, -5, .1, .1], index=dt)
+    snow_coverage = snow.coverage_nrel(
+        snowfall_data, poa_irradiance, temp_air, surface_tilt,
+        initial_coverage=0.5, threshold_snowfall=1.)
+    slide_amt = slide_amount_coefficient * sind(surface_tilt)
+    covered = 0.5 - slide_amt * np.array([0, 1, 2, 3, 4, 5, 6, 7])
+    covered = np.where(covered < 0, 0., covered)
+    expected = pd.Series(covered, index=dt)
+    assert_series_equal(expected, snow_coverage)
+
+
+def test_dc_loss_nrel():
+    num_strings = 8
+    snow_coverage = pd.Series([1, 1, .5, .6, .2, .4, 0])
+    expected = pd.Series([1, 1, .5, .625, .25, .5, 0])
+    actual = snow.dc_loss_nrel(snow_coverage, num_strings)
+    assert_series_equal(expected, actual)
diff --git a/pvlib/tests/test_spa.py b/pvlib/tests/test_spa.py
index 196acd7..70b3b90 100644
--- a/pvlib/tests/test_spa.py
+++ b/pvlib/tests/test_spa.py
@@ -98,7 +98,8 @@ mix_month_actual = mix_year_actual
 class SpaBase(object):
     """Test functions common to numpy and numba spa"""
     def test_julian_day_dt(self):
-        dt = times.tz_convert('UTC')[0]
+        # add 1us manually to the test timestamp (GH #940)
+        dt = times.tz_convert('UTC')[0] + pd.Timedelta(1, unit='us')
         year = dt.year
         month = dt.month
         day = dt.day
@@ -106,7 +107,7 @@ class SpaBase(object):
         minute = dt.minute
         second = dt.second
         microsecond = dt.microsecond
-        assert_almost_equal(JD,
+        assert_almost_equal(JD + 1e-6 / (3600*24),  # modify expected JD by 1us
                             self.spa.julian_day_dt(
                                 year, month, day, hour,
                                 minute, second, microsecond), 6)