REF: Localizer class to de-duplicate tzconversion code
State: closed

Second attempt at #46246.  Perf impact isn't _as_ bad, but is still ugly, see asvs posted below.

The lines:

```
        if info.use_utc:
            local_val = stamps[i]
        elif info.use_tzlocal:
            local_val = tz_convert_utc_to_tzlocal(stamps[i], tz)
        elif info.use_fixed:
            local_val = stamps[i] + info.delta
        else:
            pos = bisect_right_i8(tdata, stamps[i], info.ntrans) - 1
            local_val = stamps[i] + info.deltas[pos]
```

are repeated 5 times in this file,  once more with small edits in tzconversion.pyx, and potentially a couple others.  So I'd _really_ like to turn them into either a Localizer method or just a module-level function.  Doing so causes even bigger performance hits.

Also these lines are liable to change as we add zoneinfo and non-nano support, so making them a function would be helpful for keeping them in sync.

The only idea I have for troubleshooting perf is to move the `info.use_foo` checks (or the info.delta and info.deltas lookups) outside of the loop, but that doesn't seem to make any difference.

Asking for help from the big guns cc @jreback @WillAyd @realead @da-woods

```
time asv continuous -E virtualenv -f 1.01 main HEAD -b tslibs.normalize -b TimeDT64ArrToPeriodArr -b TimeIntsToPydatetime -b TimeResolution --record-samples --append-samples
[...]
       before           after         ratio
     [49937b93]       [c1085df7]
     <perf-gb-mask>       <ref-localizer-2>
+     1.62±0.05μs      2.45±0.03μs     1.51  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 5000, datetime.timezone.utc)
+     1.89±0.02ms       2.68±0.4ms     1.41  tslibs.normalize.Normalize.time_is_date_array_normalized(1000000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      4.19±0.1μs       5.70±0.6μs     1.36  tslibs.resolution.TimeResolution.time_get_resolution('us', 0, datetime.timezone(datetime.timedelta(seconds=3600)))
+      4.57±0.1μs       6.12±0.2μs     1.34  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 3000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      1.90±0.1μs       2.51±0.3μs     1.32  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 7000, None)
+     4.11±0.06μs      5.42±0.07μs     1.32  tslibs.resolution.TimeResolution.time_get_resolution('m', 1, datetime.timezone(datetime.timedelta(seconds=3600)))
+     4.00±0.09μs       5.23±0.2μs     1.31  tslibs.resolution.TimeResolution.time_get_resolution('us', 1, datetime.timezone(datetime.timedelta(seconds=3600)))
+     4.04±0.09μs       5.27±0.5μs     1.30  tslibs.resolution.TimeResolution.time_get_resolution('m', 0, datetime.timezone(datetime.timedelta(seconds=3600)))
+        974±10ns       1.26±0.1μs     1.29  tslibs.resolution.TimeResolution.time_get_resolution('ns', 0, tzlocal())
+      4.76±0.1μs       6.13±0.4μs     1.29  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 2011, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.76±0.08μs       2.26±0.1μs     1.28  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 4000, tzlocal())
+     1.77±0.02μs       2.26±0.3μs     1.28  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 6000, tzlocal())
+     1.67±0.03μs      2.13±0.01μs     1.28  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 4006, None)
+     2.12±0.07μs      2.70±0.05μs     1.27  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 4000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.70±0.05μs      2.16±0.02μs     1.27  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 3000, tzlocal())
+     1.64±0.01μs      2.07±0.04μs     1.27  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 7000, datetime.timezone.utc)
+     2.27±0.04μs      2.87±0.03μs     1.27  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 4000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+        951±10ns      1.20±0.01μs     1.26  tslibs.resolution.TimeResolution.time_get_resolution('us', 1, datetime.timezone.utc)
+        244±20μs         308±30μs     1.26  tslibs.resolution.TimeResolution.time_get_resolution('us', 10000, datetime.timezone.utc)
+      8.57±0.2μs       10.8±0.6μs     1.26  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 4000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+         923±7ns      1.16±0.04μs     1.26  tslibs.resolution.TimeResolution.time_get_resolution('s', 0, datetime.timezone.utc)
+     1.70±0.03μs      2.14±0.03μs     1.26  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 8000, None)
+     4.40±0.05μs       5.53±0.2μs     1.26  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 4000, None)
+     1.67±0.05μs      2.10±0.01μs     1.26  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 4000, None)
+     1.53±0.04μs       1.92±0.1μs     1.26  tslibs.resolution.TimeResolution.time_get_resolution('ns', 1, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     2.31±0.03μs      2.90±0.03μs     1.25  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 5000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     1.42±0.03μs      1.78±0.04μs     1.25  tslibs.resolution.TimeResolution.time_get_resolution('us', 1, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.64±0.02μs      2.05±0.03μs     1.25  tslibs.resolution.TimeResolution.time_get_resolution('m', 1, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     1.97±0.07μs       2.47±0.3μs     1.25  tslibs.normalize.Normalize.time_normalize_i8_timestamps(100, datetime.timezone.utc)
+     7.16±0.05μs       8.95±0.6μs     1.25  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 3000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     2.28±0.04μs       2.85±0.2μs     1.25  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 7000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     4.94±0.08μs       6.15±0.2μs     1.25  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 7000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      1.03±0.1μs      1.28±0.01μs     1.25  tslibs.resolution.TimeResolution.time_get_resolution('us', 1, None)
+     2.08±0.04μs      2.58±0.02μs     1.24  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 5000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+         286±9μs          355±4μs     1.24  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(10000, 9000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     1.67±0.04μs      2.07±0.03μs     1.24  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 4006, datetime.timezone.utc)
+     1.03±0.04μs         1.28±0μs     1.24  tslibs.resolution.TimeResolution.time_get_resolution('s', 0, None)
+      4.96±0.1μs       6.14±0.1μs     1.24  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 10000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     2.10±0.03μs      2.60±0.05μs     1.24  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 4006, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     4.15±0.07μs      5.13±0.03μs     1.24  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 6000, None)
+      4.68±0.1μs       5.78±0.1μs     1.24  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 2011, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      4.95±0.1μs       6.12±0.1μs     1.23  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 4006, datetime.timezone(datetime.timedelta(seconds=3600)))
+        674±20μs         832±70μs     1.23  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(10000, 9000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      4.84±0.1μs       5.96±0.2μs     1.23  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 4000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     6.37±0.09μs       7.83±0.2μs     1.23  tslibs.resolution.TimeResolution.time_get_resolution('m', 100, datetime.timezone(datetime.timedelta(seconds=3600)))
+     5.13±0.04μs       6.29±0.2μs     1.23  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 10000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      5.00±0.4μs       6.11±0.1μs     1.22  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 8000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     3.67±0.07ms      4.49±0.07ms     1.22  tslibs.resolution.TimeResolution.time_get_resolution('s', 100, tzlocal())
+     1.92±0.09μs      2.35±0.02μs     1.22  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 9000, None)
+     1.55±0.03μs      1.89±0.03μs     1.22  tslibs.resolution.TimeResolution.time_get_resolution('m', 0, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      4.92±0.3μs       6.02±0.2μs     1.22  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 5000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.06±0.03μs       1.30±0.1μs     1.22  tslibs.resolution.TimeResolution.time_get_resolution('m', 0, None)
+         208±4μs        254±0.9μs     1.22  tslibs.resolution.TimeResolution.time_get_resolution('m', 10000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     9.37±0.08μs      11.4±0.06μs     1.22  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 5000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+        27.7±2ms       33.8±0.2ms     1.22  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 7000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     2.32±0.03μs       2.83±0.1μs     1.22  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 8000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     2.30±0.03μs      2.81±0.03μs     1.22  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 6000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      5.41±0.2μs       6.60±0.6μs     1.22  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 7000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.77±0.06μs      2.16±0.02μs     1.22  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 7000, tzlocal())
+     1.45±0.01μs      1.76±0.05μs     1.22  tslibs.resolution.TimeResolution.time_get_resolution('m', 1, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     4.02±0.02μs       4.89±0.5μs     1.22  tslibs.resolution.TimeResolution.time_get_resolution('D', 1, datetime.timezone(datetime.timedelta(seconds=3600)))
+     2.46±0.08μs      2.98±0.09μs     1.22  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 10000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     2.11±0.07μs       2.56±0.2μs     1.22  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 7000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     2.97±0.02μs      3.61±0.06μs     1.21  tslibs.resolution.TimeResolution.time_get_resolution('m', 100, None)
+     2.05±0.03μs      2.48±0.08μs     1.21  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 3000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      4.87±0.2μs       5.91±0.2μs     1.21  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 6000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      7.60±0.4μs       9.21±0.2μs     1.21  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 11000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.01±0.01μs      1.22±0.03μs     1.21  tslibs.resolution.TimeResolution.time_get_resolution('us', 0, None)
+      7.10±0.2μs       8.61±0.2μs     1.21  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 1011, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.73±0.04μs      2.09±0.02μs     1.21  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 4000, datetime.timezone.utc)
+      4.11±0.2μs       4.98±0.2μs     1.21  tslibs.resolution.TimeResolution.time_get_resolution('D', 0, datetime.timezone(datetime.timedelta(seconds=3600)))
+        69.1±3ms         83.7±5ms     1.21  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 4006, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      64.8±0.6ms         78.5±4ms     1.21  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 8000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     4.02±0.05μs      4.86±0.09μs     1.21  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 1011, datetime.timezone.utc)
+      1.85±0.2μs      2.24±0.04μs     1.21  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 1000, None)
+      28.1±0.6ms         34.0±2ms     1.21  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 10000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+         205±4μs          248±1μs     1.21  tslibs.resolution.TimeResolution.time_get_resolution('m', 10000, None)
+      5.13±0.2μs       6.18±0.3μs     1.21  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 2000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     4.17±0.06μs       5.03±0.5μs     1.20  tslibs.resolution.TimeResolution.time_get_resolution('ns', 1, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.77±0.03μs      2.13±0.02μs     1.20  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 5000, tzlocal())
+         551±5μs         663±40μs     1.20  tslibs.resolution.TimeResolution.time_get_resolution('s', 10000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.72±0.04μs      2.07±0.01μs     1.20  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 5000, None)
+     5.08±0.06μs      6.11±0.04μs     1.20  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 9000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     1.76±0.04μs      2.11±0.02μs     1.20  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 6000, None)
+     2.55±0.07μs       3.06±0.3μs     1.20  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 4000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+        24.9±1μs       29.9±0.7μs     1.20  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 100, datetime.timezone.utc)
+      2.29±0.1μs      2.75±0.07μs     1.20  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 3000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      4.52±0.3μs      5.43±0.06μs     1.20  tslibs.resolution.TimeResolution.time_get_resolution('h', 1, datetime.timezone(datetime.timedelta(seconds=3600)))
+     3.56±0.06ms       4.27±0.2ms     1.20  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 11000, tzlocal())
+     1.35±0.02μs      1.62±0.01μs     1.20  tslibs.resolution.TimeResolution.time_get_resolution('s', 0, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      23.6±0.5ms       28.4±0.5ms     1.20  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 2000, datetime.timezone.utc)
+      3.84±0.2μs      4.61±0.07μs     1.20  tslibs.resolution.TimeResolution.time_get_resolution('m', 100, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      28.5±0.9ms         34.1±3ms     1.20  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 4000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      26.4±0.3ms         31.6±2ms     1.20  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 11000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     5.27±0.09μs       6.29±0.4μs     1.19  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 12000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      21.9±0.5μs       26.2±0.6μs     1.19  tslibs.normalize.Normalize.time_is_date_array_normalized(10000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      2.37±0.1μs       2.82±0.1μs     1.19  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 2000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      7.79±0.5μs       9.28±0.1μs     1.19  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 7000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      25.6±0.2μs       30.5±0.6μs     1.19  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 100, None)
+     2.31±0.08μs       2.75±0.1μs     1.19  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 2011, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     3.38±0.02ms       4.01±0.1ms     1.19  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 10000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      27.4±0.6ms         32.5±2ms     1.19  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 6000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      5.16±0.2μs       6.13±0.1μs     1.19  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 12000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.77±0.04μs      2.11±0.01μs     1.19  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 4006, tzlocal())
+     2.07±0.05μs       2.45±0.2μs     1.19  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 0, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     1.60±0.01μs      1.90±0.03μs     1.19  tslibs.resolution.TimeResolution.time_get_resolution('us', 0, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+         209±4μs          248±5μs     1.19  tslibs.resolution.TimeResolution.time_get_resolution('ns', 10000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.75±0.06μs      2.07±0.02μs     1.19  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 3000, datetime.timezone.utc)
+      2.24±0.2μs      2.65±0.02μs     1.18  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 10000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      5.42±0.1μs       6.42±0.7μs     1.18  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 4006, datetime.timezone(datetime.timedelta(seconds=3600)))
+      3.64±0.2ms      4.31±0.04ms     1.18  tslibs.resolution.TimeResolution.time_get_resolution('m', 100, tzlocal())
+     2.24±0.07μs       2.65±0.2μs     1.18  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 1, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.78±0.03μs       2.10±0.2μs     1.18  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('timestamp', 0, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     2.02±0.02μs       2.39±0.2μs     1.18  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 2011, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+        66.3±2ms         78.4±1ms     1.18  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 11000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      2.26±0.1μs       2.67±0.1μs     1.18  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 4000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      26.2±0.7μs       30.9±0.3μs     1.18  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 100, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      1.89±0.1μs      2.23±0.03μs     1.18  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 5000, None)
+         340±6ms         401±20ms     1.18  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 1000000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      25.3±0.4ms       29.9±0.5ms     1.18  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 6000, datetime.timezone.utc)
+        65.1±1ms       76.8±0.5ms     1.18  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 6000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     2.07±0.03μs       2.44±0.1μs     1.18  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 11000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      3.05±0.1μs      3.60±0.04μs     1.18  tslibs.resolution.TimeResolution.time_get_resolution('m', 100, datetime.timezone.utc)
+        67.4±1ms         79.3±3ms     1.18  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 7000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      1.46±0.1μs      1.72±0.07μs     1.18  tslibs.resolution.TimeResolution.time_get_resolution('h', 0, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.73±0.04μs       2.03±0.1μs     1.18  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 7000, None)
+     1.46±0.03μs      1.72±0.04μs     1.18  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('timestamp', 0, None)
+     1.88±0.09μs       2.21±0.1μs     1.18  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 9000, tzlocal())
+     5.28±0.02μs      6.20±0.08μs     1.17  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 4000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      2.40±0.1μs      2.82±0.07μs     1.17  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 4006, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      5.14±0.2μs      6.04±0.05μs     1.17  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 11000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     2.13±0.02μs       2.50±0.1μs     1.17  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 8000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.70±0.08μs      1.99±0.04μs     1.17  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 9000, tzlocal())
+      1.81±0.2μs      2.13±0.05μs     1.17  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 7000, datetime.timezone.utc)
+     1.75±0.02μs       2.05±0.2μs     1.17  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 1, None)
+         990±9ns      1.16±0.07μs     1.17  tslibs.resolution.TimeResolution.time_get_resolution('m', 1, datetime.timezone.utc)
+     3.57±0.04ms       4.18±0.3ms     1.17  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 10000, tzlocal())
+        207±10μs          242±3μs     1.17  tslibs.resolution.TimeResolution.time_get_resolution('m', 10000, datetime.timezone.utc)
+      8.14±0.2μs       9.52±0.3μs     1.17  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 1011, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     2.24±0.05μs       2.62±0.2μs     1.17  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 9000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      1.74±0.1μs      2.03±0.08μs     1.17  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 12000, datetime.timezone.utc)
+     1.05±0.04μs      1.23±0.02μs     1.17  tslibs.resolution.TimeResolution.time_get_resolution('m', 1, None)
+     8.17±0.04μs       9.52±0.4μs     1.17  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 5000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      2.42±0.1μs      2.82±0.03μs     1.17  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 3000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+         360±4ms         420±20ms     1.17  tslibs.resolution.TimeResolution.time_get_resolution('h', 10000, tzlocal())
+        615±10μs         715±10μs     1.16  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(10000, 1011, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.01±0.06μs      1.17±0.03μs     1.16  tslibs.resolution.TimeResolution.time_get_resolution('D', 0, tzlocal())
+     1.82±0.04μs      2.11±0.01μs     1.16  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 11000, None)
+      29.8±0.5μs       34.6±0.7μs     1.16  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 100, datetime.timezone(datetime.timedelta(seconds=3600)))
+         243±4μs         283±40μs     1.16  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(10000, 1000, datetime.timezone.utc)
+      5.12±0.3μs       5.95±0.1μs     1.16  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 10000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     4.51±0.03μs       5.24±0.1μs     1.16  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 4006, None)
+      7.70±0.2μs       8.94±0.5μs     1.16  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 4000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      2.27±0.2μs      2.63±0.07μs     1.16  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 2000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      7.95±0.1μs       9.22±0.6μs     1.16  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 2011, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.03±0.01μs      1.19±0.03μs     1.16  tslibs.resolution.TimeResolution.time_get_resolution('D', 0, None)
+     1.72±0.03μs       2.00±0.1μs     1.16  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 9000, None)
+        912±30ns      1.06±0.09μs     1.16  tslibs.resolution.TimeResolution.time_get_resolution('m', 0, datetime.timezone.utc)
+     4.26±0.08μs       4.94±0.1μs     1.16  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 1011, None)
+      2.22±0.2μs      2.57±0.08μs     1.16  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 12000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      2.51±0.2μs      2.91±0.07μs     1.16  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 1000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     4.98±0.08μs       5.76±0.2μs     1.16  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 3000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+        37.1±2μs       42.9±0.3μs     1.15  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 100, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      25.5±0.8ms       29.4±0.5ms     1.15  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 6000, None)
+      25.5±0.9ms       29.4±0.2ms     1.15  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 1011, datetime.timezone(datetime.timedelta(seconds=3600)))
+         207±6μs         238±10μs     1.15  tslibs.resolution.TimeResolution.time_get_resolution('h', 10000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     4.42±0.03μs       5.10±0.4μs     1.15  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 4000, datetime.timezone.utc)
+     4.13±0.09μs      4.76±0.02μs     1.15  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 3000, datetime.timezone.utc)
+      4.16±0.2μs      4.79±0.05μs     1.15  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 2011, datetime.timezone.utc)
+        25.8±1ms       29.7±0.2ms     1.15  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 2000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+        967±10ns      1.11±0.03μs     1.15  tslibs.resolution.TimeResolution.time_get_resolution('ns', 1, None)
+     1.75±0.04μs       2.02±0.1μs     1.15  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 8000, tzlocal())
+      1.84±0.1μs      2.11±0.02μs     1.15  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 12000, None)
+        21.8±1ms       25.0±0.3ms     1.15  tslibs.resolution.TimeResolution.time_get_resolution('m', 1000000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.81±0.05μs       2.08±0.2μs     1.15  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1, datetime.timezone.utc)
+     3.71±0.02ms       4.26±0.1ms     1.15  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 1000, tzlocal())
+        38.3±1μs         44.0±1μs     1.15  tslibs.resolution.TimeResolution.time_get_resolution('D', 1, tzlocal())
+        231±10μs          265±2μs     1.15  tslibs.resolution.TimeResolution.time_get_resolution('m', 10000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     1.85±0.08μs      2.13±0.03μs     1.15  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 3000, None)
+      1.50±0.1μs      1.72±0.03μs     1.15  tslibs.resolution.TimeResolution.time_get_resolution('h', 1, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+         373±8ms         427±10ms     1.15  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('timestamp', 10000, tzlocal())
+        57.5±2ms         65.8±1ms     1.14  tslibs.resolution.TimeResolution.time_get_resolution('s', 1000000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      7.52±0.4μs       8.60±0.7μs     1.14  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 8000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     4.90±0.07μs      5.59±0.03μs     1.14  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 1011, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      4.84±0.1μs       5.52±0.5μs     1.14  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 11000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      25.4±0.3ms         28.9±2ms     1.14  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 9000, datetime.timezone(datetime.timedelta(seconds=3600)))
+         203±5μs          231±6μs     1.14  tslibs.resolution.TimeResolution.time_get_resolution('D', 10000, None)
+     1.10±0.09μs      1.25±0.01μs     1.14  tslibs.resolution.TimeResolution.time_get_resolution('s', 0, tzlocal())
+      4.54±0.4μs      5.17±0.03μs     1.14  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 9000, None)
+     3.26±0.04μs       3.72±0.3μs     1.14  tslibs.resolution.TimeResolution.time_get_resolution('us', 100, None)
+     6.28±0.07μs       7.16±0.5μs     1.14  tslibs.resolution.TimeResolution.time_get_resolution('us', 100, datetime.timezone(datetime.timedelta(seconds=3600)))
+        25.1±1ms       28.6±0.4ms     1.14  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 1000, None)
+      26.2±0.3ms       29.9±0.8ms     1.14  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 11000, datetime.timezone.utc)
+      4.22±0.2ms      4.80±0.01ms     1.14  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('timestamp', 10000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      5.37±0.2μs       6.11±0.1μs     1.14  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 4000, datetime.timezone(datetime.timedelta(seconds=3600)))
+         354±9ms         403±20ms     1.14  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(10000, 9000, tzlocal())
+        26.7±1ms       30.4±0.9ms     1.14  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 6000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.69±0.02μs       1.92±0.1μs     1.14  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 10000, datetime.timezone.utc)
+         266±5μs         302±10μs     1.14  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(10000, 1011, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+         239±8μs         271±10μs     1.14  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(10000, 1011, datetime.timezone.utc)
+     4.25±0.07ms       4.82±0.1ms     1.13  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('timestamp', 10000, datetime.timezone.utc)
+         202±2μs         229±10μs     1.13  tslibs.resolution.TimeResolution.time_get_resolution('s', 10000, datetime.timezone.utc)
+      3.83±0.1ms       4.33±0.2ms     1.13  tslibs.resolution.TimeResolution.time_get_resolution('ns', 100, tzlocal())
+     1.74±0.04μs      1.97±0.04μs     1.13  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 6000, datetime.timezone.utc)
+     1.82±0.02μs      2.06±0.05μs     1.13  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('timestamp', 1, datetime.timezone.utc)
+      6.80±0.5μs       7.69±0.2μs     1.13  tslibs.resolution.TimeResolution.time_get_resolution('h', 100, datetime.timezone(datetime.timedelta(seconds=3600)))
+     2.61±0.09μs      2.95±0.05μs     1.13  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 1011, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+         195±3μs          220±9μs     1.13  tslibs.resolution.TimeResolution.time_get_resolution('D', 10000, datetime.timezone.utc)
+     2.12±0.07μs       2.39±0.2μs     1.13  tslibs.normalize.Normalize.time_normalize_i8_timestamps(100, None)
+      1.84±0.1μs       2.07±0.2μs     1.13  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 6000, datetime.timezone.utc)
+      2.39±0.1μs       2.69±0.2μs     1.13  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 2011, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      5.14±0.3μs      5.77±0.07μs     1.12  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 1000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+        61.1±3ms       68.6±0.3ms     1.12  tslibs.resolution.TimeResolution.time_get_resolution('m', 1000000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.88±0.06μs      2.11±0.03μs     1.12  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 11000, None)
+      1.99±0.2μs      2.23±0.03μs     1.12  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 2011, tzlocal())
+        39.6±1μs       44.4±0.6μs     1.12  tslibs.resolution.TimeResolution.time_get_resolution('m', 1, tzlocal())
+         450±3ms         504±20ms     1.12  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('timestamp', 1000000, datetime.timezone.utc)
+      1.90±0.1μs      2.13±0.02μs     1.12  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 4000, None)
+         442±3ms         495±30ms     1.12  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 1000000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     3.57±0.02ms       3.99±0.2ms     1.12  tslibs.resolution.TimeResolution.time_get_resolution('h', 100, tzlocal())
+      1.90±0.2μs      2.12±0.03μs     1.12  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 1000, tzlocal())
+     2.48±0.02μs      2.77±0.02μs     1.12  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 1011, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     2.40±0.09μs      2.68±0.06μs     1.12  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 4006, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      5.21±0.1μs       5.80±0.4μs     1.11  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 9000, datetime.timezone(datetime.timedelta(seconds=3600)))
+        42.4±2μs       47.2±0.3μs     1.11  tslibs.resolution.TimeResolution.time_get_resolution('s', 1, tzlocal())
+      3.79±0.1ms      4.21±0.01ms     1.11  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 100, tzlocal())
+     4.46±0.04μs       4.96±0.2μs     1.11  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('timestamp', 0, datetime.timezone(datetime.timedelta(seconds=3600)))
+      5.48±0.4μs      6.09±0.07μs     1.11  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 3000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      1.88±0.1μs      2.09±0.03μs     1.11  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1, None)
+      5.27±0.2μs       5.86±0.2μs     1.11  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1, datetime.timezone(datetime.timedelta(seconds=3600)))
+         223±2μs         248±20μs     1.11  tslibs.resolution.TimeResolution.time_get_resolution('s', 10000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     3.94±0.06μs      4.38±0.06μs     1.11  tslibs.resolution.TimeResolution.time_get_resolution('h', 100, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      1.83±0.1μs      2.03±0.02μs     1.11  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 4000, datetime.timezone.utc)
+      5.97±0.4μs       6.63±0.6μs     1.11  tslibs.normalize.Normalize.time_normalize_i8_timestamps(100, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.34±0.03μs      1.49±0.03μs     1.11  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('timestamp', 0, datetime.timezone.utc)
+     1.94±0.04μs      2.15±0.02μs     1.11  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 4006, None)
+     2.33±0.09μs      2.58±0.04μs     1.11  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 1000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.84±0.08μs      2.04±0.09μs     1.11  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 1000, datetime.timezone.utc)
+     2.59±0.05μs      2.86±0.02μs     1.11  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 12000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      6.05±0.1μs       6.69±0.2μs     1.11  tslibs.resolution.TimeResolution.time_get_resolution('ns', 100, datetime.timezone(datetime.timedelta(seconds=3600)))
+      7.79±0.7μs      8.60±0.03μs     1.10  tslibs.resolution.TimeResolution.time_get_resolution('h', 100, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      28.3±0.7ms       31.3±0.5ms     1.10  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      1.77±0.2μs      1.95±0.04μs     1.10  tslibs.resolution.TimeResolution.time_get_resolution('h', 1, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      5.52±0.4μs       6.06±0.1μs     1.10  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 11000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.88±0.03μs      2.07±0.04μs     1.10  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 11000, tzlocal())
+         605±8μs         664±10μs     1.10  tslibs.resolution.TimeResolution.time_get_resolution('m', 10000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+        949±20ns      1.04±0.05μs     1.10  tslibs.resolution.TimeResolution.time_get_resolution('us', 0, datetime.timezone.utc)
+      1.95±0.1μs      2.14±0.04μs     1.10  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 4000, tzlocal())
+      5.58±0.2μs      6.12±0.05μs     1.10  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 1000, datetime.timezone(datetime.timedelta(seconds=3600)))
+        919±30ns      1.01±0.03μs     1.10  tslibs.resolution.TimeResolution.time_get_resolution('ns', 1, datetime.timezone.utc)
+      4.77±0.1μs       5.21±0.3μs     1.09  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 9000, datetime.timezone(datetime.timedelta(seconds=3600)))
+         328±3ms          358±5ms     1.09  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 1000000, None)
+      3.69±0.1ms       4.03±0.2ms     1.09  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 2011, tzlocal())
+     2.07±0.01μs       2.26±0.1μs     1.09  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 9000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+         335±5ms         365±30ms     1.09  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 1000000, datetime.timezone(datetime.timedelta(seconds=3600)))
+        63.2±2ms       68.9±0.9ms     1.09  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 1000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      2.56±0.1μs      2.79±0.04μs     1.09  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      1.90±0.1μs      2.07±0.03μs     1.09  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 2000, datetime.timezone.utc)
+     1.97±0.03μs      2.14±0.07μs     1.08  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('timestamp', 1, None)
+     2.03±0.09μs      2.20±0.08μs     1.08  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('timestamp', 0, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     7.09±0.06μs       7.66±0.4μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 2000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.58±0.06μs      1.70±0.05μs     1.08  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('timestamp', 0, tzlocal())
+      35.2±0.8μs       38.0±0.7μs     1.08  tslibs.normalize.Normalize.time_is_date_array_normalized(10000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+        21.8±1ms       23.5±0.2ms     1.08  tslibs.resolution.TimeResolution.time_get_resolution('D', 1000000, None)
+      2.01±0.1μs      2.17±0.09μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 2000, tzlocal())
+     1.39±0.02μs      1.50±0.06μs     1.08  tslibs.resolution.TimeResolution.time_get_resolution('ns', 1, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     2.61±0.08μs      2.81±0.04μs     1.07  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 3000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+         338±4ms         363±20ms     1.07  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 1000000, datetime.timezone.utc)
+        549±30ns         590±20ns     1.07  tslibs.normalize.Normalize.time_is_date_array_normalized(1, None)
+      2.44±0.1μs      2.62±0.02μs     1.07  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 11000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.94±0.08μs      2.08±0.04μs     1.07  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 4006, datetime.timezone.utc)
+      5.17±0.3μs      5.52±0.04μs     1.07  tslibs.normalize.Normalize.time_normalize_i8_timestamps(100, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      3.40±0.1μs      3.63±0.02μs     1.07  tslibs.resolution.TimeResolution.time_get_resolution('h', 100, None)
+      2.67±0.1ms      2.83±0.02ms     1.06  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, datetime.timezone.utc)
+      2.52±0.1μs      2.67±0.02μs     1.06  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 11000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     1.51±0.05μs      1.57±0.02μs     1.04  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 0, tzlocal())
+      43.7±0.2μs         45.5±2μs     1.04  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('timestamp', 100, datetime.timezone(datetime.timedelta(seconds=3600)))
-     3.53±0.03ms      3.36±0.01ms     0.95  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, datetime.timezone(datetime.timedelta(seconds=3600)))
-     1.34±0.04ms      1.27±0.02ms     0.95  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('date', 10000, None)
-        279±20ms          265±4ms     0.95  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 1000000, datetime.timezone(datetime.timedelta(seconds=3600)))
-         276±9ms          261±2ms     0.95  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 1000000, datetime.timezone.utc)
-         151±3ms          142±2ms     0.94  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('date', 1000000, None)
-      2.29±0.1μs      2.09±0.04μs     0.91  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 0, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
-     1.57±0.03μs      1.41±0.09μs     0.90  tslibs.resolution.TimeResolution.time_get_resolution('ns', 0, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
-      3.54±0.2ms      3.07±0.05ms     0.87  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 10000, datetime.timezone.utc)
```

Labels:
name: Refactor
description: Internal refactoring of code
name: Timezones
description: Timezone data dtype

Comments:
can u make just a function to avoid repeating w/o creating the localizer obj?
> can u make just a function to avoid repeating w/o creating the localizer obj?

similar perf hit
Any insights in checking the generated C code before/after? My best guess is that Cython may be generating extra checks for class attribute access beyond what normal struct member access would do
This is the generated C-code for the old version:

```
    /* "pandas/_libs/tslibs/vectorized.pyx":137
 *             continue
 * 
 *         if use_utc:             # <<<<<<<<<<<<<<
 *             local_val = value
 *         elif use_tzlocal:
 */
    __pyx_t_9 = (__pyx_v_use_utc != 0);
    if (__pyx_t_9) {

      /* "pandas/_libs/tslibs/vectorized.pyx":138
 * 
 *         if use_utc:
 *             local_val = value             # <<<<<<<<<<<<<<
 *         elif use_tzlocal:
 *             local_val = tz_convert_utc_to_tzlocal(value, tz)
 */
      __pyx_v_local_val = __pyx_v_value;

      /* "pandas/_libs/tslibs/vectorized.pyx":137
 *             continue
 * 
 *         if use_utc:             # <<<<<<<<<<<<<<
 *             local_val = value
 *         elif use_tzlocal:
 */
      goto __pyx_L15;
    }

    /* "pandas/_libs/tslibs/vectorized.pyx":139
 *         if use_utc:
 *             local_val = value
 *         elif use_tzlocal:             # <<<<<<<<<<<<<<
 *             local_val = tz_convert_utc_to_tzlocal(value, tz)
 *         elif use_fixed:
 */
    __pyx_t_9 = (__pyx_v_use_tzlocal != 0);
    if (__pyx_t_9) {

      /* "pandas/_libs/tslibs/vectorized.pyx":140
 *             local_val = value
 *         elif use_tzlocal:
 *             local_val = tz_convert_utc_to_tzlocal(value, tz)             # <<<<<<<<<<<<<<
 *         elif use_fixed:
 *             local_val = value + delta
 */
      __pyx_t_24 = __pyx_f_6pandas_5_libs_6tslibs_12tzconversion_tz_convert_utc_to_tzlocal(__pyx_v_value, __pyx_v_tz, NULL); if (unlikely(__pyx_t_24 == ((__pyx_t_5numpy_int64_t)-1LL) && PyErr_Occurred())) __PYX_ERR(0, 140, __pyx_L1_error)
      __pyx_v_local_val = __pyx_t_24;

      /* "pandas/_libs/tslibs/vectorized.pyx":139
 *         if use_utc:
 *             local_val = value
 *         elif use_tzlocal:             # <<<<<<<<<<<<<<
 *             local_val = tz_convert_utc_to_tzlocal(value, tz)
 *         elif use_fixed:
 */
      goto __pyx_L15;
    }

    /* "pandas/_libs/tslibs/vectorized.pyx":141
 *         elif use_tzlocal:
 *             local_val = tz_convert_utc_to_tzlocal(value, tz)
 *         elif use_fixed:             # <<<<<<<<<<<<<<
 *             local_val = value + delta
 *         else:
 */
    __pyx_t_9 = (__pyx_v_use_fixed != 0);
    if (__pyx_t_9) {

      /* "pandas/_libs/tslibs/vectorized.pyx":142
 *             local_val = tz_convert_utc_to_tzlocal(value, tz)
 *         elif use_fixed:
 *             local_val = value + delta             # <<<<<<<<<<<<<<
 *         else:
 *             pos = bisect_right_i8(tdata, value, ntrans) - 1
 */
      __pyx_v_local_val = (__pyx_v_value + __pyx_v_delta);

      /* "pandas/_libs/tslibs/vectorized.pyx":141
 *         elif use_tzlocal:
 *             local_val = tz_convert_utc_to_tzlocal(value, tz)
 *         elif use_fixed:             # <<<<<<<<<<<<<<
 *             local_val = value + delta
 *         else:
 */
      goto __pyx_L15;
    }

    /* "pandas/_libs/tslibs/vectorized.pyx":144
 *             local_val = value + delta
 *         else:
 *             pos = bisect_right_i8(tdata, value, ntrans) - 1             # <<<<<<<<<<<<<<
 *             local_val = value + deltas[pos]
 * 
 */
    /*else*/ {
      __pyx_v_pos = (__pyx_f_6pandas_5_libs_6tslibs_12tzconversion_bisect_right_i8(__pyx_v_tdata, __pyx_v_value, __pyx_v_ntrans) - 1);

      /* "pandas/_libs/tslibs/vectorized.pyx":145
 *         else:
 *             pos = bisect_right_i8(tdata, value, ntrans) - 1
 *             local_val = value + deltas[pos]             # <<<<<<<<<<<<<<
 * 
 *             if use_pytz:
 */
      if (unlikely(!__pyx_v_deltas.memview)) { __Pyx_RaiseUnboundLocalError("deltas"); __PYX_ERR(0, 145, __pyx_L1_error) }
      __pyx_t_19 = __pyx_v_pos;
      __pyx_v_local_val = (__pyx_v_value + (*((__pyx_t_5numpy_int64_t *) ( /* dim=0 */ ((char *) (((__pyx_t_5numpy_int64_t *) __pyx_v_deltas.data) + __pyx_t_19)) ))));
```

and this is the generated C-code for the new version:

```
   /* "pandas/_libs/tslibs/vectorized.pyx":168
 *             continue
 * 
 *         if info.use_utc:             # <<<<<<<<<<<<<<
 *             local_val = value
 *         elif info.use_tzlocal:
 */
    __pyx_t_7 = (__pyx_v_info->use_utc != 0);
    if (__pyx_t_7) {

      /* "pandas/_libs/tslibs/vectorized.pyx":169
 * 
 *         if info.use_utc:
 *             local_val = value             # <<<<<<<<<<<<<<
 *         elif info.use_tzlocal:
 *             local_val = tz_convert_utc_to_tzlocal(value, tz)
 */
      __pyx_v_local_val = __pyx_v_value;

      /* "pandas/_libs/tslibs/vectorized.pyx":168
 *             continue
 * 
 *         if info.use_utc:             # <<<<<<<<<<<<<<
 *             local_val = value
 *         elif info.use_tzlocal:
 */
      goto __pyx_L8;
    }

    /* "pandas/_libs/tslibs/vectorized.pyx":170
 *         if info.use_utc:
 *             local_val = value
 *         elif info.use_tzlocal:             # <<<<<<<<<<<<<<
 *             local_val = tz_convert_utc_to_tzlocal(value, tz)
 *         elif info.use_fixed:
 */
    __pyx_t_7 = (__pyx_v_info->use_tzlocal != 0);
    if (__pyx_t_7) {

      /* "pandas/_libs/tslibs/vectorized.pyx":171
 *             local_val = value
 *         elif info.use_tzlocal:
 *             local_val = tz_convert_utc_to_tzlocal(value, tz)             # <<<<<<<<<<<<<<
 *         elif info.use_fixed:
 *             local_val = value + info.delta
 */
      __pyx_t_14 = __pyx_f_6pandas_5_libs_6tslibs_12tzconversion_tz_convert_utc_to_tzlocal(__pyx_v_value, __pyx_v_tz, NULL); if (unlikely(__pyx_t_14 == ((__pyx_t_5numpy_int64_t)-1LL) && PyErr_Occurred())) __PYX_ERR(1, 171, __pyx_L1_error)
      __pyx_v_local_val = __pyx_t_14;

      /* "pandas/_libs/tslibs/vectorized.pyx":170
 *         if info.use_utc:
 *             local_val = value
 *         elif info.use_tzlocal:             # <<<<<<<<<<<<<<
 *             local_val = tz_convert_utc_to_tzlocal(value, tz)
 *         elif info.use_fixed:
 */
      goto __pyx_L8;
    }

    /* "pandas/_libs/tslibs/vectorized.pyx":172
 *         elif info.use_tzlocal:
 *             local_val = tz_convert_utc_to_tzlocal(value, tz)
 *         elif info.use_fixed:             # <<<<<<<<<<<<<<
 *             local_val = value + info.delta
 *         else:
 */
    __pyx_t_7 = (__pyx_v_info->use_fixed != 0);
    if (__pyx_t_7) {

      /* "pandas/_libs/tslibs/vectorized.pyx":173
 *             local_val = tz_convert_utc_to_tzlocal(value, tz)
 *         elif info.use_fixed:
 *             local_val = value + info.delta             # <<<<<<<<<<<<<<
 *         else:
 *             pos = bisect_right_i8(tdata, value, info.ntrans) - 1
 */
      __pyx_v_local_val = (__pyx_v_value + __pyx_v_info->delta);

      /* "pandas/_libs/tslibs/vectorized.pyx":172
 *         elif info.use_tzlocal:
 *             local_val = tz_convert_utc_to_tzlocal(value, tz)
 *         elif info.use_fixed:             # <<<<<<<<<<<<<<
 *             local_val = value + info.delta
 *         else:
 */
      goto __pyx_L8;
    }

    /* "pandas/_libs/tslibs/vectorized.pyx":175
 *             local_val = value + info.delta
 *         else:
 *             pos = bisect_right_i8(tdata, value, info.ntrans) - 1             # <<<<<<<<<<<<<<
 *             local_val = value + info.deltas[pos]
 * 
 */
    /*else*/ {
      __pyx_v_pos = (__pyx_f_6pandas_5_libs_6tslibs_12tzconversion_bisect_right_i8(__pyx_v_tdata, __pyx_v_value, __pyx_v_info->ntrans) - 1);

      /* "pandas/_libs/tslibs/vectorized.pyx":176
 *         else:
 *             pos = bisect_right_i8(tdata, value, info.ntrans) - 1
 *             local_val = value + info.deltas[pos]             # <<<<<<<<<<<<<<
 * 
 *             if info.use_pytz:
 */
      if (unlikely(!__pyx_v_info->deltas.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(1, 176, __pyx_L1_error)}
      __pyx_t_12 = __pyx_v_pos;
      __pyx_v_local_val = (__pyx_v_value + (*((__pyx_t_5numpy_int64_t const  *) ( /* dim=0 */ ((char *) (((__pyx_t_5numpy_int64_t const  *) __pyx_v_info->deltas.data) + __pyx_t_12)) ))));
```

Nothing that would catch my eye... Do we know which of the branches is responsible for the slowdown or do all of them become slower?
> Do we know which of the branches is responsible for the slowdown or do all of them become slower?

I don't see any clear pattern, no.
> Nothing that would catch my eye... Do we know which of the branches is responsible for the slowdown or do all of them become slower?

(At the bottom of this post I'll paste the full up-to-date asv results on the rebased branch.)

The most heavily impacted is `normalize_i8_timestamps`, so I'll focus on that and how tinkering with that function affects the results.  The worst 4 results are

```
+     2.40±0.06ms       3.96±0.2ms     1.65  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, datetime.timezone(datetime.timedelta(seconds=3600)))
+        30.6±2μs         48.3±1μs     1.58  tslibs.normalize.Normalize.time_normalize_i8_timestamps(10000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     3.56±0.08ms       5.24±0.4ms     1.47  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+        39.5±2μs         57.5±3μs     1.46  tslibs.normalize.Normalize.time_normalize_i8_timestamps(10000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
```

If I add to the declarations `cdef bint use_utc=info.use_utc, use_tzlocal=info.use_tzlocal, use_fixed info.use_fixed` and change the in-loop checks from `if info.use_foo` to just `if use_foo`, things get appreciably better:

```
+        30.3±2μs       38.7±0.8μs     1.27  tslibs.normalize.Normalize.time_normalize_i8_timestamps(10000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      3.63±0.1ms       4.46±0.2ms     1.23  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      2.55±0.2ms      3.09±0.07ms     1.21  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, datetime.timezone(datetime.timedelta(seconds=3600)))
```

(pretty similar on a re-run)

If I do the same thing to change out `info.delta` with just `delta`, i get disparate results across four consecutive runs:

```
# run 1
+      3.59±0.1ms       4.25±0.2ms     1.18  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))

# run 2
+      30.0±0.4μs         37.4±1μs     1.25  tslibs.normalize.Normalize.time_normalize_i8_timestamps(10000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     4.68±0.07μs       5.25±0.2μs     1.12  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1, datetime.timezone(datetime.timedelta(seconds=3600)))

# run 3
-        33.6±3ms       26.1±0.4ms     0.78  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
-      3.43±0.3ms      2.61±0.05ms     0.76  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, None)

# run 4
+      2.39±0.1ms       3.89±0.4ms     1.63  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     3.50±0.08ms       4.65±0.2ms     1.33  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      30.5±0.6μs         38.7±2μs     1.27  tslibs.normalize.Normalize.time_normalize_i8_timestamps(10000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      4.74±0.1μs       5.89±0.3μs     1.24  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1, datetime.timezone(datetime.timedelta(seconds=3600)))
+      39.2±0.3μs       48.2±0.9μs     1.23  tslibs.normalize.Normalize.time_normalize_i8_timestamps(10000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
```


> Any insights in checking the generated C code before/after? My best guess is that Cython may be generating extra checks for class attribute access beyond what normal struct member access would do

There's a line in the loop that is is currently `local_val = utc_val + deltas[pos]` that this branch changes to use `info.deltas[pos]`.  The generated C for the former is:

```
      if (unlikely(!__pyx_v_deltas.memview)) { __Pyx_RaiseUnboundLocalError("deltas"); __PYX_ERR(0, 146, __pyx_L1_error) }
      __pyx_t_18 = __pyx_v_pos;
      __pyx_v_local_val = (__pyx_v_utc_val + (*((__pyx_t_5numpy_int64_t *) ( /* dim=0 */ ((char *) (((__pyx_t_5numpy_int64_t *) __pyx_v_deltas.data) + __pyx_t_18)) ))));
```

and the new version:

```
      if (unlikely(!__pyx_v_info->deltas.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(1, 176, __pyx_L1_error)}
      __pyx_t_11 = __pyx_v_pos;
      __pyx_v_local_val = (__pyx_v_utc_val + (*((__pyx_t_5numpy_int64_t const  *) ( /* dim=0 */ ((char *) (((__pyx_t_5numpy_int64_t const  *) __pyx_v_info->deltas.data) + __pyx_t_11)) ))));
```

In the latter case my read of the cython docs is that `@cython.initializedcheck(False)` should disable it, but in local profiling that decoration has pretty consistently slowed things down.

Full results:

```
       before           after         ratio
     [afec0e9c]       [41f89cfe]
     <enh-nano-ts>       <ref-localizer-2>
+     2.40±0.06ms       3.96±0.2ms     1.65  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, datetime.timezone(datetime.timedelta(seconds=3600)))
+        30.6±2μs         48.3±1μs     1.58  tslibs.normalize.Normalize.time_normalize_i8_timestamps(10000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     3.56±0.08ms       5.24±0.4ms     1.47  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+        39.5±2μs         57.5±3μs     1.46  tslibs.normalize.Normalize.time_normalize_i8_timestamps(10000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+        502±10ns         652±20ns     1.30  tslibs.normalize.Normalize.time_is_date_array_normalized(1, tzlocal())
+        90.9±1μs          116±8μs     1.28  tslibs.normalize.Normalize.time_is_date_array_normalized(10000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     2.77±0.05ms       3.54±0.2ms     1.28  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, datetime.timezone.utc)
+      4.11±0.2μs       5.19±0.3μs     1.26  tslibs.resolution.TimeResolution.time_get_resolution('us', 1, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.04±0.06μs      1.31±0.09μs     1.26  tslibs.resolution.TimeResolution.time_get_resolution('us', 1, None)
+      4.17±0.1μs       5.24±0.4μs     1.26  tslibs.resolution.TimeResolution.time_get_resolution('D', 0, datetime.timezone(datetime.timedelta(seconds=3600)))
+        531±20ns         664±50ns     1.25  tslibs.normalize.Normalize.time_is_date_array_normalized(0, tzlocal())
+     2.79±0.09ms       3.47±0.2ms     1.24  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, None)
+        929±70ns      1.14±0.07μs     1.23  tslibs.resolution.TimeResolution.time_get_resolution('s', 0, datetime.timezone.utc)
+         206±5μs         251±20μs     1.22  tslibs.resolution.TimeResolution.time_get_resolution('h', 10000, datetime.timezone.utc)
+        19.9±1ms         24.2±2ms     1.22  tslibs.resolution.TimeResolution.time_get_resolution('ns', 1000000, datetime.timezone.utc)
+     1.00±0.05μs       1.22±0.1μs     1.21  tslibs.resolution.TimeResolution.time_get_resolution('s', 0, None)
+      5.12±0.4μs       6.19±0.3μs     1.21  tslibs.normalize.Normalize.time_normalize_i8_timestamps(100, datetime.timezone(datetime.timedelta(seconds=3600)))
+     2.32±0.03μs       2.79±0.2μs     1.21  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 1, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     2.00±0.07μs       2.40±0.1μs     1.20  tslibs.normalize.Normalize.time_is_date_array_normalized(100, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     1.50±0.04μs       1.80±0.2μs     1.20  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 0, tzlocal())
+     1.48±0.06μs       1.77±0.1μs     1.20  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 0, None)
+      20.8±0.7ms         25.0±1ms     1.20  tslibs.resolution.TimeResolution.time_get_resolution('m', 1000000, None)
+      6.29±0.3μs       7.51±0.8μs     1.19  tslibs.resolution.TimeResolution.time_get_resolution('ns', 100, datetime.timezone(datetime.timedelta(seconds=3600)))
+      20.9±0.4ms         25.0±1ms     1.19  tslibs.resolution.TimeResolution.time_get_resolution('m', 1000000, datetime.timezone.utc)
+     1.50±0.05μs      1.79±0.09μs     1.19  tslibs.resolution.TimeResolution.time_get_resolution('D', 0, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     1.61±0.07μs       1.91±0.1μs     1.19  tslibs.resolution.TimeResolution.time_get_resolution('m', 0, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      6.37±0.4μs       7.54±0.4μs     1.18  tslibs.resolution.TimeResolution.time_get_resolution('D', 100, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.03±0.03μs       1.22±0.1μs     1.18  tslibs.resolution.TimeResolution.time_get_resolution('m', 0, None)
+        559±50ns         660±60ns     1.18  tslibs.normalize.Normalize.time_is_date_array_normalized(1, None)
+      3.46±0.3μs       4.07±0.3μs     1.18  tslibs.normalize.Normalize.time_is_date_array_normalized(0, datetime.timezone(datetime.timedelta(seconds=3600)))
+     2.31±0.09μs       2.71±0.2μs     1.18  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 1, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+        463±30ns         543±30ns     1.17  tslibs.normalize.Normalize.time_is_date_array_normalized(1, datetime.timezone.utc)
+        955±30ns      1.12±0.06μs     1.17  tslibs.resolution.TimeResolution.time_get_resolution('m', 1, datetime.timezone.utc)
+     2.07±0.05μs       2.42±0.2μs     1.17  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 0, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     1.00±0.03μs      1.17±0.06μs     1.17  tslibs.resolution.TimeResolution.time_get_resolution('h', 0, tzlocal())
+      4.83±0.2μs       5.62±0.4μs     1.16  tslibs.normalize.Normalize.time_normalize_i8_timestamps(0, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.68±0.04μs       1.95±0.2μs     1.16  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 1, None)
+         200±8μs         233±30μs     1.16  tslibs.resolution.TimeResolution.time_get_resolution('ns', 10000, None)
+     1.87±0.09μs       2.17±0.2μs     1.16  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 0, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.44±0.06μs       1.67±0.1μs     1.16  tslibs.resolution.TimeResolution.time_get_resolution('D', 1, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      1.47±0.1μs       1.70±0.1μs     1.16  tslibs.resolution.TimeResolution.time_get_resolution('m', 1, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+        933±60ns      1.08±0.08μs     1.16  tslibs.resolution.TimeResolution.time_get_resolution('us', 0, datetime.timezone.utc)
+     1.64±0.06μs       1.89±0.2μs     1.16  tslibs.resolution.TimeResolution.time_get_resolution('us', 1, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      2.55±0.1μs       2.95±0.2μs     1.16  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('timestamp', 1, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      3.94±0.2μs       4.54±0.5μs     1.15  tslibs.resolution.TimeResolution.time_get_resolution('us', 100, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     1.36±0.06μs       1.56±0.1μs     1.15  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 0, datetime.timezone.utc)
+        941±30ns      1.08±0.09μs     1.15  tslibs.resolution.TimeResolution.time_get_resolution('h', 0, datetime.timezone.utc)
+         547±4ms         629±50ms     1.15  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('timestamp', 1000000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.39±0.07μs      1.60±0.08μs     1.15  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 0, datetime.timezone.utc)
+     1.35±0.04μs      1.55±0.09μs     1.15  tslibs.resolution.TimeResolution.time_get_resolution('D', 0, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      7.52±0.4μs       8.62±0.8μs     1.15  tslibs.resolution.TimeResolution.time_get_resolution('us', 100, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+        536±60ns         613±80ns     1.14  tslibs.normalize.Normalize.time_is_date_array_normalized(0, None)
+     1.56±0.02μs       1.79±0.1μs     1.14  tslibs.resolution.TimeResolution.time_get_resolution('h', 0, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      3.06±0.2μs       3.49±0.2μs     1.14  tslibs.resolution.TimeResolution.time_get_resolution('D', 100, None)
+      1.64±0.1μs       1.87±0.1μs     1.14  tslibs.resolution.TimeResolution.time_get_resolution('s', 1, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+        81.1±3μs         92.5±8μs     1.14  tslibs.normalize.Normalize.time_is_date_array_normalized(10000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.35±0.02μs       1.53±0.2μs     1.14  tslibs.resolution.TimeResolution.time_get_resolution('us', 0, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+        990±30ns       1.13±0.1μs     1.14  tslibs.resolution.TimeResolution.time_get_resolution('us', 1, datetime.timezone.utc)
+      4.19±0.2μs       4.78±0.4μs     1.14  tslibs.resolution.TimeResolution.time_get_resolution('m', 0, datetime.timezone(datetime.timedelta(seconds=3600)))
+      3.10±0.1μs       3.52±0.4μs     1.14  tslibs.resolution.TimeResolution.time_get_resolution('ns', 100, datetime.timezone.utc)
+      2.97±0.1μs       3.37±0.2μs     1.14  tslibs.resolution.TimeResolution.time_get_resolution('D', 100, datetime.timezone.utc)
+         208±5μs         236±10μs     1.14  tslibs.resolution.TimeResolution.time_get_resolution('h', 10000, None)
+     1.08±0.06μs      1.22±0.05μs     1.13  tslibs.resolution.TimeResolution.time_get_resolution('D', 1, None)
+      4.98±0.1μs       5.64±0.2μs     1.13  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 1, datetime.timezone(datetime.timedelta(seconds=3600)))
+      4.37±0.1μs       4.95±0.3μs     1.13  tslibs.normalize.Normalize.time_is_date_array_normalized(100, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.64±0.04μs       1.85±0.2μs     1.13  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('date', 1, None)
+     1.43±0.06μs       1.62±0.1μs     1.13  tslibs.resolution.TimeResolution.time_get_resolution('us', 1, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.01±0.04μs      1.14±0.07μs     1.13  tslibs.resolution.TimeResolution.time_get_resolution('h', 1, datetime.timezone.utc)
+     1.08±0.05μs      1.22±0.04μs     1.13  tslibs.resolution.TimeResolution.time_get_resolution('h', 1, None)
+      7.45±0.4μs       8.42±0.7μs     1.13  tslibs.resolution.TimeResolution.time_get_resolution('s', 100, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      4.32±0.1μs       4.86±0.3μs     1.12  tslibs.resolution.TimeResolution.time_get_resolution('m', 1, datetime.timezone(datetime.timedelta(seconds=3600)))
+      8.01±0.3ms       9.00±0.7ms     1.12  tslibs.normalize.Normalize.time_is_date_array_normalized(1000000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      7.10±0.5μs       7.96±0.3μs     1.12  tslibs.resolution.TimeResolution.time_get_resolution('D', 100, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+        38.6±2μs         43.2±3μs     1.12  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 1, tzlocal())
+     1.02±0.05μs      1.14±0.08μs     1.12  tslibs.resolution.TimeResolution.time_get_resolution('s', 0, tzlocal())
+     1.04±0.06μs      1.17±0.04μs     1.12  tslibs.resolution.TimeResolution.time_get_resolution('D', 0, tzlocal())
+        987±20ns       1.10±0.2μs     1.12  tslibs.resolution.TimeResolution.time_get_resolution('us', 0, None)
+        27.3±1ms         30.5±2ms     1.12  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      2.48±0.1ms      2.77±0.09ms     1.12  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 10000, None)
+         211±8μs         236±10μs     1.12  tslibs.resolution.TimeResolution.time_get_resolution('h', 10000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      4.69±0.3μs       5.22±0.5μs     1.11  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 0, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.03±0.04μs      1.15±0.08μs     1.11  tslibs.resolution.TimeResolution.time_get_resolution('D', 0, None)
+      3.26±0.1μs       3.63±0.4μs     1.11  tslibs.resolution.TimeResolution.time_get_resolution('us', 100, None)
+      4.60±0.1μs       5.09±0.6μs     1.11  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 0, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.47±0.06μs       1.62±0.1μs     1.10  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('date', 0, None)
+      9.53±0.4ms       10.5±0.8ms     1.10  tslibs.normalize.Normalize.time_is_date_array_normalized(1000000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      3.61±0.1ms       3.98±0.3ms     1.10  tslibs.normalize.Normalize.time_is_date_array_normalized(100, tzlocal())
+         211±5μs         232±10μs     1.10  tslibs.resolution.TimeResolution.time_get_resolution('D', 10000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      3.84±0.1μs       4.23±0.3μs     1.10  tslibs.resolution.TimeResolution.time_get_resolution('D', 100, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+         255±3ms         281±20ms     1.10  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 1000000, None)
+        479±40ns         525±40ns     1.09  tslibs.normalize.Normalize.time_is_date_array_normalized(0, datetime.timezone.utc)
+         265±2ms         289±20ms     1.09  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 1000000, datetime.timezone.utc)
+     1.01±0.02μs      1.10±0.08μs     1.09  tslibs.resolution.TimeResolution.time_get_resolution('h', 0, None)
+        991±30ns       1.08±0.2μs     1.08  tslibs.resolution.TimeResolution.time_get_resolution('ns', 0, None)
+     1.02±0.07μs      1.10±0.09μs     1.08  tslibs.resolution.TimeResolution.time_get_resolution('ns', 0, tzlocal())
+      7.04±0.3μs       7.55±0.7μs     1.07  tslibs.resolution.TimeResolution.time_get_resolution('s', 100, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.50±0.05μs       1.60±0.1μs     1.07  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 0, tzlocal())
+      38.0±0.8μs         40.5±3μs     1.07  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 1, tzlocal())
+     1.54±0.04μs       1.64±0.2μs     1.06  tslibs.resolution.TimeResolution.time_get_resolution('ns', 0, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      36.3±0.7ms         38.6±2ms     1.06  tslibs.normalize.Normalize.time_is_date_array_normalized(1000000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.83±0.03μs       1.91±0.1μs     1.05  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 0, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+         356±4ms         370±10ms     1.04  tslibs.resolution.TimeResolution.time_get_resolution('D', 10000, tzlocal())
-        504±30ms         440±20ms     0.87  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('timestamp', 1000000, None)
```
I've had a bit of a look at this, and unfortunately can't see anything obvious to me.

The `Localizer` class potentially looks to add a bit of reference counting/allocation overhead (that I know you've tried to avoid with `@freelist`), and doesn't hugely feel like it's functioning very well as a useful class (i.e. you never return it, and haven't don't look to have been able to attach any useful functions to it).

Hopefully i can find a bit of time to have a go at tinkering with it (instead of just looking) in the next few days though.
> I've had a bit of a look at this, and

I really appreciate your taking time to help here as I've hit a wall.  Please let me know if I can reciprocate.

> reference counting/allocation overhead (that I know you've tried to avoid with @freelist)

Yah, it's also the one place where I find adding `@cython.initializedcheck(False)` seemed to improve perf instead of making it worse.  Trimming it down was part of the motivation behind https://groups.google.com/g/cython-users/c/oeKJX4Hq6mI

>  and doesn't hugely feel like it's functioning very well as a useful class (i.e. you never return it, and haven't don't look to have been able to attach any useful functions to it).

Yah, this PR that just makes the class is pared down from #46246 that also had a utc_val_to_local_val.  That had an even worse performance hit, hence this less-ambitious PR first.  Getting `utc_val_to_local_val` in just one place would be the ideal outcome, as we would re-use it in 2 places in tzconversion.pyx and I think one in conversion.pyx
Is there any plausible chance that making some or all of the relevant code nogil will fix the perf hit?  Doing so is a minefield, but I may be possible. 
> Is there any plausible chance that making some or all of the relevant code nogil will fix the perf hit? Doing so is a minefield, but I may be possible.

Nogil has no benefit on it's own. The benefit is

1. that you can run nogil code in parallel
2. that nogil code is often the sort of code that runs quite quickly (i.e. they're correlated, but it isn't actually the `nogil` that makes it quick)
Based off what @realead provided and what @da-woods clarified, my current best guess is that the construction of the class is what might be causing the slowdown. I see this in the newly generated C code:

```c
  /* "pandas/_libs/tslibs/vectorized.pyx":133
 *     """
 *     cdef:
 *         Localizer info = Localizer(tz)             # <<<<<<<<<<<<<<
 *         int64_t utc_val, local_val
 *         Py_ssize_t pos, i, n = stamps.shape[0]
 */
  __pyx_t_1 = __Pyx_PyObject_CallOneArg(((PyObject *)__pyx_ptype_6pandas_5_libs_6tslibs_10vectorized_Localizer), ((PyObject *)__pyx_v_tz)); if (unlikely(!__pyx_t_1)) __PYX_ERR(1, 133, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  __pyx_v_info = ((struct __pyx_obj_6pandas_5_libs_6tslibs_10vectorized_Localizer *)__pyx_t_1);
  __pyx_t_1 = 0;
```

So IIUC we might still be adding some overhead by calling PyObject_CallOneArg which wouldn't have existed in the previous version? I wonder if  a separate C method that takes a Localizer struct along with tzinfo and sets the struct attributes instead of a \_\_cinit\_\_ constructor shows any performance difference
@jbrockmendel 

i think u need to create a c struct 

then just pass it to/from functions

eg don't create anything in python land at all
I did have a go at a struct-based implementation myself. I didn't manage to get it working quite right (it was reportedly failing some of the tests), and it didn't seem to be faster on the ones it was passing.

The complication is that you can't easily have any reference-counted fields (i.e. memoryviews or python objects) on the struct. I suspect this is where I went wrong in hindsight - the memoryview can be replaced by a pointer and a length, but you do need to find a way to keep the object that it views alive (which I think I neglected)
> my current best guess is that the construction of the class is what might be causing the slowdown

This seems easy to test, right?  Just add a Localizer construction in each of the relevant functions and then don't even use it.  See how perf is affected?

The only other hypothesis is that at some level the compiler taking the `if use_utc:` checks outside of the for loop in the status quo, but isn't do the same with `if info.use_utc:`
Yea that makes sense to me. Unless it gets optimized away (compiler will warn for this as unused variable)
@WillAyd 

> Yea that makes sense to me.

I don't suppose that was in reference to my "compiler takes the checks outside the loop hypothesis?"

OK, I tried just creating Localizer but never actually using it.  Recorded samples over 4 runs to try to de-noise the whole thing.

```
time asv continuous -E virtualenv -f 1.01 main HEAD -b tslibs.normalize --record-samples --append-samples
[...]
     [c3ab0c99]       [8580f2e9]
     <enh-nano-ts>       <localizer-3>
+      3.57±0.3μs       7.03±0.5μs     1.97  tslibs.normalize.Normalize.time_is_date_array_normalized(1, datetime.timezone(datetime.timedelta(seconds=3600)))
+      3.78±0.3μs       6.57±0.5μs     1.74  tslibs.normalize.Normalize.time_is_date_array_normalized(0, datetime.timezone(datetime.timedelta(seconds=3600)))
+      5.23±0.3μs       8.83±0.2μs     1.69  tslibs.normalize.Normalize.time_normalize_i8_timestamps(100, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.14±0.09μs      1.90±0.04μs     1.66  tslibs.normalize.Normalize.time_is_date_array_normalized(0, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      4.52±0.3μs       7.32±0.2μs     1.62  tslibs.normalize.Normalize.time_is_date_array_normalized(100, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.16±0.06μs      1.85±0.04μs     1.60  tslibs.normalize.Normalize.time_is_date_array_normalized(1, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+        906±60ns       1.38±0.1μs     1.52  tslibs.normalize.Normalize.time_is_date_array_normalized(0, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+        867±50ns      1.31±0.01μs     1.51  tslibs.normalize.Normalize.time_is_date_array_normalized(1, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      5.50±0.5μs       8.26±0.2μs     1.50  tslibs.normalize.Normalize.time_normalize_i8_timestamps(0, datetime.timezone(datetime.timedelta(seconds=3600)))
+      5.61±0.4μs       8.27±0.4μs     1.47  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1, datetime.timezone(datetime.timedelta(seconds=3600)))
+      2.05±0.1μs       2.90±0.2μs     1.42  tslibs.normalize.Normalize.time_is_date_array_normalized(100, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+        524±30ns         716±20ns     1.37  tslibs.normalize.Normalize.time_is_date_array_normalized(1, None)
+        535±30ns         710±30ns     1.33  tslibs.normalize.Normalize.time_is_date_array_normalized(1, tzlocal())
+        589±40ns         738±50ns     1.25  tslibs.normalize.Normalize.time_is_date_array_normalized(0, tzlocal())
+      2.41±0.1μs       2.96±0.1μs     1.23  tslibs.normalize.Normalize.time_normalize_i8_timestamps(0, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      2.74±0.2μs      3.31±0.05μs     1.21  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      2.90±0.1μs      3.48±0.08μs     1.20  tslibs.normalize.Normalize.time_normalize_i8_timestamps(100, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      2.62±0.2μs       3.13±0.1μs     1.19  tslibs.normalize.Normalize.time_normalize_i8_timestamps(0, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+        561±50ns         665±50ns     1.19  tslibs.normalize.Normalize.time_is_date_array_normalized(0, None)
+        35.0±2μs         41.2±1μs     1.18  tslibs.normalize.Normalize.time_normalize_i8_timestamps(10000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      2.46±0.1μs       2.83±0.2μs     1.15  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+        488±30ns         556±20ns     1.14  tslibs.normalize.Normalize.time_is_date_array_normalized(0, datetime.timezone.utc)
+      4.55±0.4μs       5.10±0.2μs     1.12  tslibs.normalize.Normalize.time_is_date_array_normalized(100, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+        496±30ns         547±10ns     1.10  tslibs.normalize.Normalize.time_is_date_array_normalized(1, datetime.timezone.utc)
+        41.2±2μs         44.7±1μs     1.09  tslibs.normalize.Normalize.time_normalize_i8_timestamps(10000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      2.87±0.2ms       3.10±0.2ms     1.08  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, datetime.timezone(datetime.timedelta(seconds=3600)))
-        86.5±5μs         78.3±1μs     0.91  tslibs.normalize.Normalize.time_is_date_array_normalized(10000, datetime.timezone.utc)
-        310±10μs         268±10μs     0.86  tslibs.normalize.Normalize.time_normalize_i8_timestamps(10000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
-        31.6±1ms       27.2±0.8ms     0.86  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
```

We should be seeing pure O(1) overhead, so in this exercise the most-affected cases should be the one with small N, which is almost exactly what we're seeing.  We don't see any changes of more than 10 microseocnds until the last 4 entries, and of those 3 are clearly-just-noise speedups.  Disregarding those last 4, the mean slowdown was 1394ns.


One last approach I can think of would be to scrap the class-based approach and go purely functional, with something taking pointer arguments to the `use_utc` and `use_tzlocal` and simply updating them in the function body

Struct based approach looks pretty hard given the amount of Python objects that are currently in the mix, including the return value of `get_dst_info`
> Struct based approach looks pretty hard given the amount of Python objects that are currently in the mix, including the return value of get_dst_info

I've thought of this w/r/t get_dst_info and think we _may_ get some improvement by returning a struct instead of a string for the 3rd return value (since we later do e.g. `if typ not in ["pytz", "dateutil"]:...` but I agree with your more general point that we're not going to get much mileage at the later stages.

Aside from trial/error and "just look at the generated code", are there any profiling tools that would help identify the problem(s)?

Other alternatives that come to mind:
- tempita
- implement some/all of the relevant code directly in C
- choose to be OK with code duplication
- choose to be OK with a perf hit

> choose to be OK with a perf hit

so the question here is sure some of these microbenchmarks are slower, but can you show the effect of a real operation (e.g. localizing a sizable DTI). what kind of perf is lost there?
> localizing a sizable DTI

localizing doesn't use the path(s) in this PR yet.

Observation someone may be able to make something of.  The most-affected benchmark (from a few minutes ago) was `time_normalize_i8_timestamps`.  `normalize_i8_timestamps` uses a cimported `normalize_i8_timesstamp`, which itself is declared as nogil.  Since `normalize_i8_timestamp` is essentially a 1-liner, I tried inlining it into `normalize_i8_timestamps` (this required adding `@cython.cdivision(False)` to `normalize_i8_timestamps`).

With this change, the formerly-worst benchmark is now at 1.09x, and the new worst one is 536ns vs 592ns.

Dies this produce an "a-ha!" moment for anyone on the thread?


Marking as ready for review, the perf impact is now an improvement for big-array cases and a sub-microsecond hit for the super-small cases:

```
time asv continuous -E virtualenv -f 1.01 main HEAD -b tslibs.normalize -b TimeDT64ArrToPeriodArr -b TimeIntsToPydatetime -b TimeResolution --record-samples --append-samples
[...]
       before           after         ratio
     [c90294db]       [ba9f3a8e]
     <perf-gb-mask>       <ref-localizer-2>
+        430±20ns         517±60ns     1.20  tslibs.normalize.Normalize.time_is_date_array_normalized(1, datetime.timezone.utc)
+        512±10ns         614±50ns     1.20  tslibs.normalize.Normalize.time_is_date_array_normalized(1, tzlocal())
+        498±20ns         593±40ns     1.19  tslibs.normalize.Normalize.time_is_date_array_normalized(1, None)
+        441±50ns         524±40ns     1.19  tslibs.normalize.Normalize.time_is_date_array_normalized(0, datetime.timezone.utc)
+      1.69±0.1μs       1.98±0.1μs     1.17  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 4000, tzlocal())
+        502±30ns         586±40ns     1.17  tslibs.normalize.Normalize.time_is_date_array_normalized(0, None)
+        79.4±5μs         92.7±8μs     1.17  tslibs.normalize.Normalize.time_is_date_array_normalized(10000, datetime.timezone(datetime.timedelta(seconds=3600)))
+        87.7±3μs          102±9μs     1.17  tslibs.normalize.Normalize.time_is_date_array_normalized(10000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     2.07±0.08μs       2.37±0.1μs     1.15  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 1000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     4.73±0.05μs       5.41±0.5μs     1.14  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 2011, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     1.55±0.02μs      1.78±0.05μs     1.14  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 12000, datetime.timezone.utc)
+        515±20ns         589±30ns     1.14  tslibs.normalize.Normalize.time_is_date_array_normalized(0, tzlocal())
+     1.74±0.09μs       1.98±0.2μs     1.14  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 4000, None)
+      9.04±0.5ms       10.2±0.5ms     1.13  tslibs.normalize.Normalize.time_is_date_array_normalized(1000000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      3.81±0.1μs       4.31±0.2μs     1.13  tslibs.resolution.TimeResolution.time_get_resolution('us', 0, datetime.timezone(datetime.timedelta(seconds=3600)))
+        901±40ns      1.02±0.02μs     1.13  tslibs.resolution.TimeResolution.time_get_resolution('m', 1, datetime.timezone.utc)
+      3.37±0.1μs       3.79±0.2μs     1.13  tslibs.normalize.Normalize.time_is_date_array_normalized(1, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.69±0.05μs       1.89±0.1μs     1.12  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 6000, tzlocal())
+     1.60±0.04μs      1.79±0.08μs     1.12  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 8000, datetime.timezone.utc)
+     1.70±0.03μs       1.90±0.2μs     1.12  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 7000, None)
+      4.20±0.1μs       4.69±0.3μs     1.12  tslibs.normalize.Normalize.time_is_date_array_normalized(100, datetime.timezone(datetime.timedelta(seconds=3600)))
+     2.09±0.04μs       2.33±0.2μs     1.11  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 7000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      6.90±0.3μs       7.68±0.4μs     1.11  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 3000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.73±0.09μs      1.91±0.07μs     1.11  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 8000, None)
+      4.61±0.1μs       5.10±0.1μs     1.11  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 9000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      4.67±0.2μs       5.16±0.2μs     1.11  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 2000, datetime.timezone(datetime.timedelta(seconds=3600)))
+        985±30ns      1.09±0.02μs     1.11  tslibs.resolution.TimeResolution.time_get_resolution('D', 1, None)
+     1.66±0.02μs      1.84±0.09μs     1.10  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 2011, None)
+     1.68±0.05μs       1.85±0.1μs     1.10  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 1000, None)
+     1.60±0.04μs       1.76±0.1μs     1.10  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 9000, datetime.timezone.utc)
+     1.78±0.05μs       1.96±0.2μs     1.10  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 1011, tzlocal())
+     1.01±0.03μs      1.11±0.05μs     1.10  tslibs.resolution.TimeResolution.time_get_resolution('us', 1, None)
+     1.59±0.02μs      1.75±0.09μs     1.10  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 9000, datetime.timezone.utc)
+     1.61±0.05μs       1.77±0.2μs     1.10  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 2011, datetime.timezone.utc)
+        922±30ns      1.01±0.03μs     1.10  tslibs.resolution.TimeResolution.time_get_resolution('D', 1, datetime.timezone.utc)
+      4.60±0.2μs      5.03±0.05μs     1.09  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 6000, datetime.timezone(datetime.timedelta(seconds=3600)))
+        832±20ns         909±50ns     1.09  tslibs.normalize.Normalize.time_is_date_array_normalized(1, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     2.06±0.06μs       2.25±0.2μs     1.09  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 2000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.58±0.04μs      1.73±0.06μs     1.09  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 10000, datetime.timezone.utc)
+        879±20ns         959±20ns     1.09  tslibs.resolution.TimeResolution.time_get_resolution('D', 0, datetime.timezone.utc)
+        981±30ns      1.07±0.03μs     1.09  tslibs.resolution.TimeResolution.time_get_resolution('h', 0, tzlocal())
+     4.27±0.07μs       4.65±0.4μs     1.09  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 4000, datetime.timezone.utc)
+        831±30ns         904±90ns     1.09  tslibs.normalize.Normalize.time_is_date_array_normalized(0, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      3.48±0.3μs       3.78±0.3μs     1.09  tslibs.normalize.Normalize.time_is_date_array_normalized(0, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.60±0.03μs      1.74±0.06μs     1.09  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 2000, datetime.timezone.utc)
+     2.02±0.08μs       2.20±0.1μs     1.09  tslibs.normalize.Normalize.time_is_date_array_normalized(100, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     1.34±0.05μs      1.46±0.02μs     1.09  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 0, datetime.timezone.utc)
+     1.72±0.08μs       1.86±0.1μs     1.09  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 9000, tzlocal())
+      4.77±0.1μs       5.17±0.3μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 4006, datetime.timezone(datetime.timedelta(seconds=3600)))
+        960±30ns      1.04±0.03μs     1.08  tslibs.resolution.TimeResolution.time_get_resolution('ns', 0, None)
+     1.58±0.03μs      1.72±0.06μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 7000, datetime.timezone.utc)
+     1.68±0.06μs       1.82±0.1μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 12000, None)
+     1.57±0.05μs      1.70±0.03μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 1000, datetime.timezone.utc)
+      1.04±0.1μs      1.13±0.08μs     1.08  tslibs.normalize.Normalize.time_is_date_array_normalized(0, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      7.19±0.3μs       7.78±0.2μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 10000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      4.81±0.3μs       5.20±0.4μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 4000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.69±0.05μs       1.82±0.1μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 11000, tzlocal())
+     1.74±0.03μs       1.88±0.2μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 9000, None)
+     1.43±0.04μs      1.55±0.05μs     1.08  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 0, tzlocal())
+     1.58±0.05μs      1.70±0.06μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 1000, datetime.timezone.utc)
+     1.68±0.06μs      1.82±0.03μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 1011, None)
+     3.97±0.03μs       4.28±0.1μs     1.08  tslibs.resolution.TimeResolution.time_get_resolution('D', 1, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.73±0.01μs      1.87±0.06μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 11000, tzlocal())
+     1.72±0.03μs       1.86±0.1μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 2000, tzlocal())
+      4.82±0.1μs       5.19±0.2μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 4000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.67±0.06μs      1.80±0.08μs     1.08  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1, None)
+        968±20ns      1.04±0.01μs     1.08  tslibs.resolution.TimeResolution.time_get_resolution('D', 0, None)
+     1.58±0.03μs      1.71±0.03μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 11000, datetime.timezone.utc)
+     1.67±0.04μs       1.80±0.1μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 4006, tzlocal())
+     1.73±0.04μs      1.86±0.08μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 8000, tzlocal())
+     1.32±0.03μs      1.42±0.04μs     1.08  tslibs.resolution.TimeResolution.time_get_resolution('s', 0, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     4.81±0.05μs       5.17±0.4μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 12000, datetime.timezone(datetime.timedelta(seconds=3600)))
+        931±20ns      1.00±0.01μs     1.08  tslibs.resolution.TimeResolution.time_get_resolution('us', 1, datetime.timezone.utc)
+     1.40±0.04μs      1.51±0.02μs     1.08  tslibs.resolution.TimeResolution.time_get_resolution('D', 1, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.58±0.05μs      1.70±0.05μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 3000, datetime.timezone.utc)
+     1.72±0.08μs      1.85±0.08μs     1.08  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 12000, tzlocal())
+      3.89±0.1μs      4.18±0.08μs     1.08  tslibs.resolution.TimeResolution.time_get_resolution('h', 0, datetime.timezone(datetime.timedelta(seconds=3600)))
+      7.77±0.4ms       8.36±0.3ms     1.08  tslibs.normalize.Normalize.time_is_date_array_normalized(1000000, datetime.timezone(datetime.timedelta(seconds=3600)))
+        988±30ns      1.06±0.03μs     1.07  tslibs.resolution.TimeResolution.time_get_resolution('m', 1, None)
+     1.71±0.06μs       1.84±0.1μs     1.07  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 2000, tzlocal())
+     1.68±0.04μs      1.80±0.05μs     1.07  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 10000, None)
+     2.30±0.07μs      2.47±0.08μs     1.07  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 9000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      4.71±0.3μs       5.05±0.3μs     1.07  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 12000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.68±0.04μs      1.80±0.06μs     1.07  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 11000, None)
+        981±40ns      1.05±0.03μs     1.07  tslibs.resolution.TimeResolution.time_get_resolution('us', 0, None)
+      4.71±0.1μs      5.04±0.06μs     1.07  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 11000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     2.26±0.09μs       2.41±0.1μs     1.07  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 12000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+      7.39±0.3μs       7.90±0.4μs     1.07  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 12000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      7.10±0.2μs       7.58±0.1μs     1.07  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 6000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.62±0.04μs      1.72±0.09μs     1.07  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 8000, datetime.timezone.utc)
+     1.69±0.04μs      1.81±0.04μs     1.07  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 1000, None)
+      6.97±0.3μs       7.43±0.2μs     1.07  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 2000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.56±0.02μs      1.66±0.03μs     1.07  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 5000, datetime.timezone.utc)
+     2.83±0.07μs      3.01±0.04μs     1.06  tslibs.resolution.TimeResolution.time_get_resolution('D', 100, datetime.timezone.utc)
+     1.51±0.03μs      1.61±0.02μs     1.06  tslibs.resolution.TimeResolution.time_get_resolution('D', 0, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     1.69±0.04μs      1.80±0.03μs     1.06  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 3000, None)
+     4.09±0.08μs      4.35±0.09μs     1.06  tslibs.resolution.TimeResolution.time_get_resolution('m', 1, datetime.timezone(datetime.timedelta(seconds=3600)))
+      6.92±0.3μs       7.35±0.5μs     1.06  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 1000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      7.04±0.2μs       7.48±0.5μs     1.06  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 2011, datetime.timezone(datetime.timedelta(seconds=3600)))
+     4.15±0.06μs      4.40±0.07μs     1.06  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 10000, datetime.timezone.utc)
+      6.02±0.2μs      6.38±0.08μs     1.06  tslibs.resolution.TimeResolution.time_get_resolution('m', 100, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.70±0.04μs       1.80±0.1μs     1.06  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 1011, None)
+     1.71±0.04μs       1.81±0.2μs     1.06  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 1000, tzlocal())
+     1.02±0.03μs      1.09±0.02μs     1.06  tslibs.resolution.TimeResolution.time_get_resolution('D', 0, tzlocal())
+     1.60±0.06μs      1.69±0.08μs     1.06  tslibs.normalize.Normalize.time_normalize_i8_timestamps(0, datetime.timezone.utc)
+     1.73±0.04μs      1.83±0.07μs     1.06  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1, tzlocal())
+        989±40ns      1.05±0.06μs     1.06  tslibs.resolution.TimeResolution.time_get_resolution('m', 0, tzlocal())
+     1.04±0.05μs      1.10±0.07μs     1.06  tslibs.normalize.Normalize.time_is_date_array_normalized(1, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     3.85±0.07μs       4.07±0.2μs     1.06  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 1011, datetime.timezone.utc)
+      7.22±0.2μs       7.63±0.2μs     1.06  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 7000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      4.70±0.1μs       4.96±0.2μs     1.06  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 5000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     2.06±0.05μs      2.17±0.07μs     1.06  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 12000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.71±0.06μs       1.80±0.1μs     1.06  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 7000, tzlocal())
+     2.29±0.07μs      2.41±0.07μs     1.05  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 3000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     3.97±0.07μs       4.19±0.3μs     1.05  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 1000, None)
+      7.93±0.2μs       8.36±0.1μs     1.05  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 5000, datetime.timezone(datetime.timedelta(seconds=3600)))
+        898±20ns         945±30ns     1.05  tslibs.resolution.TimeResolution.time_get_resolution('ns', 0, datetime.timezone.utc)
+     2.08±0.05μs      2.19±0.04μs     1.05  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 3000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     4.01±0.09μs       4.22±0.2μs     1.05  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 2011, None)
+     4.41±0.08μs       4.64±0.3μs     1.05  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 4000, None)
+     1.33±0.03μs      1.40±0.04μs     1.05  tslibs.resolution.TimeResolution.time_get_resolution('D', 0, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+      7.34±0.2μs       7.72±0.2μs     1.05  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 9000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      4.81±0.2μs       5.05±0.3μs     1.05  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 8000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      4.85±0.2μs       5.09±0.3μs     1.05  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 1000, datetime.timezone(datetime.timedelta(seconds=3600)))
+      3.99±0.1μs      4.19±0.09μs     1.05  tslibs.resolution.TimeResolution.time_get_resolution('us', 1, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.77±0.08μs      1.86±0.02μs     1.05  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 1, None)
+     1.69±0.03μs      1.77±0.03μs     1.05  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 1000, tzlocal())
+        907±30ns         950±20ns     1.05  tslibs.resolution.TimeResolution.time_get_resolution('us', 0, datetime.timezone.utc)
+     1.71±0.04μs       1.79±0.1μs     1.05  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 5000, tzlocal())
+     1.61±0.05μs      1.69±0.09μs     1.05  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 4006, datetime.timezone.utc)
+     2.06±0.06μs      2.15±0.08μs     1.05  tslibs.normalize.Normalize.time_normalize_i8_timestamps(0, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
+     1.70±0.06μs       1.78±0.1μs     1.05  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 12000, None)
+     4.02±0.09μs      4.21±0.09μs     1.05  tslibs.resolution.TimeResolution.time_get_resolution('ns', 0, datetime.timezone(datetime.timedelta(seconds=3600)))
+      18.8±0.4ms       19.6±0.3ms     1.05  tslibs.resolution.TimeResolution.time_get_resolution('ns', 1000000, None)
+     1.74±0.07μs      1.81±0.09μs     1.04  tslibs.normalize.Normalize.time_normalize_i8_timestamps(0, tzlocal())
+     1.61±0.06μs      1.68±0.04μs     1.04  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1, datetime.timezone.utc)
+         266±4μs         277±20μs     1.04  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(10000, 9000, datetime.timezone(datetime.timedelta(seconds=3600)))
+     1.69±0.04μs      1.76±0.04μs     1.04  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(0, 5000, None)
+      2.36±0.1μs       2.45±0.2μs     1.04  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1, 11000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
+     1.30±0.03μs      1.34±0.09μs     1.03  tslibs.normalize.Normalize.time_is_date_array_normalized(100, None)
+     5.07±0.08μs      5.21±0.06μs     1.03  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(100, 11000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
-         324±7ms          318±3ms     0.98  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 1000000, datetime.timezone.utc)
-         247±7μs          243±3μs     0.98  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(10000, 3000, datetime.timezone.utc)
-         325±7ms          319±2ms     0.98  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 1000000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
-      27.4±0.4ms       26.8±0.3ms     0.98  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 12000, None)
-         429±8ms          420±5ms     0.98  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('timestamp', 1000000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
-        25.8±1ms       25.2±0.3ms     0.98  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 11000, datetime.timezone.utc)
-      14.4±0.3μs       14.0±0.1μs     0.98  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('date', 100, None)
-      3.07±0.2ms      3.00±0.04ms     0.98  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('datetime', 10000, None)
-         353±5ms          345±2ms     0.98  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('time', 10000, tzlocal())
-         267±6μs          258±5μs     0.97  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(10000, 11000, datetime.timezone.utc)
-      26.0±0.5ms       25.1±0.2ms     0.97  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 11000, None)
-      25.3±0.9ms       24.4±0.4ms     0.96  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 2011, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
-      20.3±0.7ms       19.5±0.3ms     0.96  tslibs.resolution.TimeResolution.time_get_resolution('ns', 1000000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
-        24.2±1ms       23.2±0.4ms     0.96  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 1000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
-      24.5±0.6ms       23.4±0.4ms     0.96  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 3000, datetime.timezone.utc)
-      24.2±0.6ms       23.1±0.3ms     0.96  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 1011, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
-        61.5±3ms       58.8±0.6ms     0.96  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 3000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
-        69.4±2ms       66.3±0.7ms     0.95  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 4006, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
-        69.8±2ms       66.6±0.6ms     0.95  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 4000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
-        290±10μs          277±4μs     0.95  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(10000, 4006, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
-         248±5μs          236±2μs     0.95  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(10000, 1000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
-     1.29±0.05ms      1.23±0.01ms     0.95  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('date', 10000, None)
-         144±5ms          138±1ms     0.95  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('date', 1000000, None)
-      27.2±0.9ms       25.9±0.3ms     0.95  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 6000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
-        28.8±1ms       27.3±0.3ms     0.95  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 4006, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
-        27.3±1ms       25.9±0.1ms     0.95  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 7000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
-      11.9±0.5ms       11.3±0.4ms     0.95  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
-      5.86±0.3μs       5.53±0.2μs     0.94  tslibs.normalize.Normalize.time_normalize_i8_timestamps(100, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
-        25.4±3ms       24.0±0.3ms     0.94  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 2000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
-         126±8μs          118±5μs     0.94  tslibs.normalize.Normalize.time_normalize_i8_timestamps(10000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
-        620±30μs         583±10μs     0.94  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(10000, 3000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
-     2.71±0.09μs       2.54±0.2μs     0.94  tslibs.normalize.Normalize.time_normalize_i8_timestamps(100, None)
-        452±20ms          421±2ms     0.93  tslibs.tslib.TimeIntsToPydatetime.time_ints_to_pydatetime('timestamp', 1000000, datetime.timezone(datetime.timedelta(seconds=3600)))
-     2.60±0.07μs      2.41±0.07μs     0.93  tslibs.normalize.Normalize.time_normalize_i8_timestamps(100, datetime.timezone.utc)
-        384±20μs         356±10μs     0.93  tslibs.normalize.Normalize.time_normalize_i8_timestamps(10000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
-        38.3±1ms         35.3±2ms     0.92  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
-      29.8±0.7ms       27.3±0.3ms     0.92  tslibs.period.TimeDT64ArrToPeriodArr.time_dt64arr_to_periodarr(1000000, 4000, tzfile('/usr/share/zoneinfo/Asia/Tokyo'))
-        77.7±3μs         69.4±5μs     0.89  tslibs.normalize.Normalize.time_is_date_array_normalized(10000, datetime.timezone.utc)
-      8.00±0.3ms       6.95±0.2ms     0.87  tslibs.normalize.Normalize.time_is_date_array_normalized(1000000, None)
-        80.2±3μs         69.7±3μs     0.87  tslibs.normalize.Normalize.time_is_date_array_normalized(10000, None)
-      8.07±0.4ms       6.96±0.3ms     0.86  tslibs.normalize.Normalize.time_is_date_array_normalized(1000000, datetime.timezone.utc)
-      10.4±0.5ms       7.82±0.8ms     0.75  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, datetime.timezone.utc)
-      10.4±0.4ms       7.75±0.4ms     0.74  tslibs.normalize.Normalize.time_normalize_i8_timestamps(1000000, None)
-         109±5μs         80.6±5μs     0.74  tslibs.normalize.Normalize.time_normalize_i8_timestamps(10000, None)
-         107±3μs         79.6±3μs     0.74  tslibs.normalize.Normalize.time_normalize_i8_timestamps(10000, datetime.timezone.utc)
```
> Also this might set up for abstracting library dependent tz offset implementations?

Not sure what you have in mind here.
I remember a some feature discussion a while back about user's being able to provide their own timezone offset implementations. This `Localizer` class looks to be conducive to do something like   

```
class PytzLocalizer(Localizer):
    def __cinit__(self, tzinfo tz):
        if tz is pytz.utc or tz is None:
             ....

    
class DatetulLocalizer(Localizer):
    def __cinit__(self, tzinfo tz):
        if tz is dateutil.tz.utc() or tz is None:
             ....

class MyTZLocalizer(Localizer):
    def __cinit__(self, tzinfo tz):
        if tz is ...:
             ....
```

in the future?
> This Localizer class looks to be conducive to do something like [...]

So the two main possibilities for next steps either look like a) what you wrote with pytz/dateutil/zoneinfo/fallback-specific implementations or b) GH#46511. My initial intuition was that the subclass-specific route would be more performant, but the last time I experimented with that it didn't pan out that way.  It'll be worth re-checking.

> user's being able to provide their own timezone offset implementations

if you mean support for arbitrary tzinfo subclasses, then absolutely.  That actually becomes really easy as a follow-up to #46425, as the general case will end up going through the tzlocal/zoneinfo path.

If you mean allowing users to implement Localizer subclasses, that sounds like a pretty big hassle.
Cool. Yeah having user implementing `Localizer` seems a bit heavy handed as hopefully there should be a single standard path to do tz arithmetic with integers, so generally the direction sounds good.
very nice @jbrockmendel