POC: move to_offset to libfrequencies
State: closed

The runtime non-cython import of to_offset is a sticking point in a bunch of tslibs code.  The reason we haven't moved to_offset up into cython is because it requires all of tseries.offsets, and we haven't wanted to move all of that up.

This moves to_offset up by defining appropriate dictionaries in the cython module and then filling them in the python modules.  It's a bit roundabout, but may be worthwhile (about to run asvs)

The refactor that was left out of this PR is moving tslibs.resolution.Resolution into tslibs.frequencies to avoid a circular/runtime import.

Labels:
name: Frequency
description: DateOffsets

Comments:
# [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24833?src=pr&el=h1) Report
> Merging [#24833](https://codecov.io/gh/pandas-dev/pandas/pull/24833?src=pr&el=desc) into [master](https://codecov.io/gh/pandas-dev/pandas/commit/a07d68582cdc49daf3e6a14a1e1fd7064670d696?src=pr&el=desc) will **decrease** coverage by `<.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/pandas-dev/pandas/pull/24833/graphs/tree.svg?width=650&token=eZ4WkYLtcO&height=150&src=pr)](https://codecov.io/gh/pandas-dev/pandas/pull/24833?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master   #24833      +/-   ##
==========================================
- Coverage   92.38%   92.38%   -0.01%     
==========================================
  Files         166      166              
  Lines       52382    52309      -73     
==========================================
- Hits        48394    48324      -70     
+ Misses       3988     3985       -3
```

| Flag | Coverage Δ | |
|---|---|---|
| #multiple | `90.8% <100%> (-0.01%)` | :arrow_down: |
| #single | `42.87% <100%> (-0.04%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/pandas-dev/pandas/pull/24833?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pandas/tseries/frequencies.py](https://codecov.io/gh/pandas-dev/pandas/pull/24833/diff?src=pr&el=tree#diff-cGFuZGFzL3RzZXJpZXMvZnJlcXVlbmNpZXMucHk=) | `97% <100%> (-0.09%)` | :arrow_down: |
| [pandas/tseries/offsets.py](https://codecov.io/gh/pandas-dev/pandas/pull/24833/diff?src=pr&el=tree#diff-cGFuZGFzL3RzZXJpZXMvb2Zmc2V0cy5weQ==) | `96.7% <100%> (ø)` | :arrow_up: |
| [pandas/util/testing.py](https://codecov.io/gh/pandas-dev/pandas/pull/24833/diff?src=pr&el=tree#diff-cGFuZGFzL3V0aWwvdGVzdGluZy5weQ==) | `88.13% <0%> (+0.09%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24833?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24833?src=pr&el=footer). Last update [a07d685...d49272c](https://codecov.io/gh/pandas-dev/pandas/pull/24833?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

# [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24833?src=pr&el=h1) Report
> Merging [#24833](https://codecov.io/gh/pandas-dev/pandas/pull/24833?src=pr&el=desc) into [master](https://codecov.io/gh/pandas-dev/pandas/commit/f4458c18287288562b21adece524fc1b046e9724?src=pr&el=desc) will **decrease** coverage by `<.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/pandas-dev/pandas/pull/24833/graphs/tree.svg?width=650&token=eZ4WkYLtcO&height=150&src=pr)](https://codecov.io/gh/pandas-dev/pandas/pull/24833?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master   #24833      +/-   ##
==========================================
- Coverage   92.39%   92.38%   -0.01%     
==========================================
  Files         166      166              
  Lines       52378    52319      -59     
==========================================
- Hits        48393    48337      -56     
+ Misses       3985     3982       -3
```

| Flag | Coverage Δ | |
|---|---|---|
| #multiple | `90.81% <100%> (-0.01%)` | :arrow_down: |
| #single | `42.86% <100%> (-0.05%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/pandas-dev/pandas/pull/24833?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pandas/tseries/frequencies.py](https://codecov.io/gh/pandas-dev/pandas/pull/24833/diff?src=pr&el=tree#diff-cGFuZGFzL3RzZXJpZXMvZnJlcXVlbmNpZXMucHk=) | `97.87% <100%> (+0.17%)` | :arrow_up: |
| [pandas/tseries/offsets.py](https://codecov.io/gh/pandas-dev/pandas/pull/24833/diff?src=pr&el=tree#diff-cGFuZGFzL3RzZXJpZXMvb2Zmc2V0cy5weQ==) | `96.7% <100%> (ø)` | :arrow_up: |
| [pandas/core/generic.py](https://codecov.io/gh/pandas-dev/pandas/pull/24833/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZ2VuZXJpYy5weQ==) | `96.63% <0%> (ø)` | :arrow_up: |
| [pandas/core/reshape/merge.py](https://codecov.io/gh/pandas-dev/pandas/pull/24833/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvcmVzaGFwZS9tZXJnZS5weQ==) | `94.47% <0%> (+0.06%)` | :arrow_up: |
| [pandas/util/testing.py](https://codecov.io/gh/pandas-dev/pandas/pull/24833/diff?src=pr&el=tree#diff-cGFuZGFzL3V0aWwvdGVzdGluZy5weQ==) | `88.13% <0%> (+0.09%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24833?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24833?src=pr&el=footer). Last update [f4458c1...461dff5](https://codecov.io/gh/pandas-dev/pandas/pull/24833?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

how did the asv work out? any change? need to merge master
> how did the asv work out? any change?

Nothing major on a subset; still running the full suite.
```
asv continuous -f 1.1 -E virtualenv master HEAD
[...]
    before     after       ratio
  [a07d6858] [d49272c8]
+  201.40ms   317.56ms      1.58  binary_ops.Ops2.time_frame_dot
+   10.93μs    13.57μs      1.24  multiindex_object.GetLoc.time_string_get_loc
+  265.13ns   320.14ns      1.21  timestamp.TimestampProperties.time_days_in_month(None, None)
+   20.37ms    24.28ms      1.19  stat_ops.FrameOps.time_op('median', 'float', 1, True)
+   29.29ms    34.82ms      1.19  frame_ctor.FromDicts.time_list_of_dict
+   24.18μs    28.74μs      1.19  offset.OffestDatetimeArithmetic.time_subtract_10(<SemiMonthBegin: day_of_month=15>)
+   19.43μs    23.01μs      1.18  offset.OffestDatetimeArithmetic.time_add_10(<BusinessQuarterEnd: startingMonth=3>)
+   10.34ms    12.24ms      1.18  timeseries.Iteration.time_iter_preexit(<function period_range at 0x7fc68510b510>)
+   14.51μs    17.17μs      1.18  offset.OffestDatetimeArithmetic.time_add(<QuarterEnd: startingMonth=3>)
+    3.72μs     4.39μs      1.18  index_object.Indexing.time_get_loc_sorted('String')
+   11.23ms    13.15ms      1.17  multiindex_object.GetLoc.time_med_get_loc_warm
+  652.60μs   755.30μs      1.16  categoricals.Constructor.time_existing_categorical
+    2.00ms     2.31ms      1.15  stat_ops.SeriesOps.time_op('sem', 'int', False)
+    7.23ms     8.32ms      1.15  gil.ParallelRolling.time_rolling('var')
+    2.51ms     2.88ms      1.14  categoricals.Concat.time_union
+   91.76ms   104.36ms      1.14  indexing.NumericSeriesIndexing.time_loc_array(<class 'pandas.core.indexes.numeric.UInt64Index'>, 'nonunique_monotonic_inc')
+  255.22ns   288.76ns      1.13  timestamp.TimestampProperties.time_is_quarter_end(None, None)
+    3.02μs     3.41μs      1.13  timedelta.TimedeltaConstructor.time_from_np_timedelta
+  251.92ms   283.43ms      1.13  gil.ParallelGroupbyMethods.time_parallel(8, 'mean')
+  410.02μs   460.38μs      1.12  indexing.NumericSeriesIndexing.time_loc_list_like(<class 'pandas.core.indexes.numeric.Int64Index'>, 'unique_monotonic_inc')
+   24.52μs    27.48μs      1.12  offset.OffestDatetimeArithmetic.time_apply_np_dt64(<Day>)
+  883.44ns   989.90ns      1.12  period.PeriodProperties.time_property('min', 'second')
+    1.95ms     2.19ms      1.12  categoricals.Concat.time_concat
+  312.78μs   350.04μs      1.12  indexing.NumericSeriesIndexing.time_loc_list_like(<class 'pandas.core.indexes.numeric.Float64Index'>, 'nonunique_monotonic_inc')
+   14.87μs    16.63μs      1.12  dtypes.Dtypes.time_pandas_dtype('int32')
+  551.10ns   615.57ns      1.12  timestamp.TimestampOps.time_to_pydatetime(None)
+    1.72μs     1.92μs      1.12  timestamp.TimestampConstruction.time_parse_now
+    3.31μs     3.69μs      1.12  categoricals.CategoricalSlicing.time_getitem_scalar('monotonic_decr')
+   19.43ms    21.68ms      1.12  stat_ops.FrameOps.time_op('median', 'int', 1, False)
+   66.00ms    73.58ms      1.11  gil.ParallelDatetimeFields.time_datetime_field_daysinmonth
+   32.45ms    36.14ms      1.11  groupby.Nth.time_frame_nth_any('float64')
+   19.14ms    21.31ms      1.11  binary_ops.Timeseries.time_timestamp_ops_diff_with_shift('US/Eastern')
+   19.46μs    21.66μs      1.11  offset.OffestDatetimeArithmetic.time_add_10(<BusinessYearEnd: month=12>)
+  115.66ms   128.55ms      1.11  gil.ParallelGroupbyMethods.time_parallel(4, 'sum')
+  135.53μs   150.40μs      1.11  offset.OffestDatetimeArithmetic.time_subtract(<CustomBusinessMonthBegin>)
+   12.04μs    13.36μs      1.11  index_object.Indexing.time_get_loc('Float')
+    1.19ms     1.32ms      1.11  binary_ops.Timeseries.time_timestamp_series_compare('US/Eastern')
+   32.02μs    35.50μs      1.11  offset.OffestDatetimeArithmetic.time_add_10(<Day>)
+    2.40ms     2.65ms      1.11  stat_ops.Correlation.time_corr('pearson', True)
+   20.11μs    22.22μs      1.10  offset.OffestDatetimeArithmetic.time_subtract(<BusinessQuarterBegin: startingMonth=3>)
+  144.95μs   160.06μs      1.10  groupby.GroupByMethods.time_dtype_as_field('datetime', 'count', 'direct')
+   21.46μs    23.67μs      1.10  offset.OffestDatetimeArithmetic.time_add(<CustomBusinessDay>)
+    1.83μs     2.02μs      1.10  timestamp.TimestampOps.time_replace_None(<UTC>)
+    8.85μs     9.76μs      1.10  indexing.NonNumericSeriesIndexing.time_getitem_scalar('datetime', 'nonunique_monotonic_inc')
+   60.72ms    66.88ms      1.10  gil.ParallelFactorize.time_parallel(4)
-    7.48μs     6.80μs      0.91  timestamp.TimestampProperties.time_month_name(<UTC>, 'B')
-    8.18ms     7.43ms      0.91  gil.ParallelRolling.time_rolling('std')
-  202.03ns   183.42ns      0.91  timedelta.TimedeltaProperties.time_timedelta_microseconds
-   23.63ms    21.44ms      0.91  reshape.Cut.time_qcut_int(4)
-   13.80μs    12.52μs      0.91  offset.OffestDatetimeArithmetic.time_apply(<MonthBegin>)
-   50.91ms    46.16ms      0.91  timedelta.ToTimedeltaErrors.time_convert('coerce')
-    3.75ms     3.40ms      0.91  rolling.VariableWindowMethods.time_rolling('DataFrame', '1h', 'int', 'skew')
-    1.49ms     1.35ms      0.90  inference.NumericInferOps.time_divide(<class 'numpy.uint8'>)
-    7.63μs     6.90μs      0.90  timestamp.TimestampProperties.time_month_name(tzutc(), 'B')
-   11.66μs    10.53μs      0.90  timestamp.TimestampProperties.time_is_month_start(<UTC>, 'B')
-   21.21μs    19.16μs      0.90  offset.OffestDatetimeArithmetic.time_add_10(<YearEnd: month=12>)
-   17.64ms    15.91ms      0.90  eval.Eval.time_add('python', 1)
-   15.99μs    14.42μs      0.90  offset.OffestDatetimeArithmetic.time_apply_np_dt64(<SemiMonthEnd: day_of_month=15>)
-   29.03μs    26.09μs      0.90  offset.OffestDatetimeArithmetic.time_apply(<Day>)
-   11.76μs    10.53μs      0.90  timestamp.TimestampProperties.time_is_year_start(<UTC>, 'B')
-   19.15ms    17.09ms      0.89  strings.Methods.time_slice
-  386.49μs   344.80μs      0.89  indexing.NumericSeriesIndexing.time_loc_list_like(<class 'pandas.core.indexes.numeric.Int64Index'>, 'nonunique_monotonic_inc')
-   15.61ms    13.91ms      0.89  timedelta.ToTimedelta.time_convert_string_days
-    3.20ms     2.85ms      0.89  offset.OffsetSeriesArithmetic.time_add_offset(<SemiMonthEnd: day_of_month=15>)
-   19.66ms    17.45ms      0.89  stat_ops.FrameOps.time_op('sem', 'float', 1, True)
-   33.80μs    29.98μs      0.89  timestamp.TimestampProperties.time_is_month_end(<DstTzInfo 'Europe/Amsterdam' LMT+0:20:00 STD>, 'B')
-  367.94μs   325.63μs      0.88  stat_ops.SeriesOps.time_op('mean', 'float', True)
-   10.25ms     9.03ms      0.88  gil.ParallelRolling.time_rolling('min')
-    2.75ms     2.43ms      0.88  timeseries.DatetimeIndex.time_normalize('repeated')
-   23.26μs    20.48μs      0.88  offset.OffestDatetimeArithmetic.time_subtract(<BusinessMonthBegin>)
-   10.29ms     9.06ms      0.88  period.PeriodIndexConstructor.time_from_ints('D', True)
-   24.37μs    21.43μs      0.88  offset.OffestDatetimeArithmetic.time_subtract(<BusinessQuarterEnd: startingMonth=3>)
-  158.88ms   139.25ms      0.88  timeseries.ToDatetimeFormatQuarters.time_infer_quarter
-   25.98μs    22.73μs      0.88  offset.OffestDatetimeArithmetic.time_subtract_10(<YearBegin: month=1>)
-    1.61ms     1.41ms      0.87  stat_ops.FrameOps.time_op('prod', 'int', 1, True)
-    4.23μs     3.69μs      0.87  indexing.CategoricalIndexIndexing.time_getitem_scalar('monotonic_decr')
-    2.97ms     2.58ms      0.87  offset.OffsetSeriesArithmetic.time_add_offset(<SemiMonthBegin: day_of_month=15>)
-   10.78ms     9.35ms      0.87  groupby.AggFunctions.time_different_numpy_functions
-    1.47ms     1.27ms      0.86  stat_ops.FrameOps.time_op('prod', 'int', 0, True)
-    2.50ms     2.16ms      0.86  indexing.NumericSeriesIndexing.time_getitem_scalar(<class 'pandas.core.indexes.numeric.UInt64Index'>, 'nonunique_monotonic_inc')
-   16.70ms    14.38ms      0.86  stat_ops.FrameOps.time_op('sem', 'int', 0, True)
-   12.50μs    10.74μs      0.86  timestamp.TimestampProperties.time_is_year_end(<UTC>, 'B')
-   13.50μs    11.57μs      0.86  multiindex_object.GetLoc.time_med_get_loc
-    2.72ms     2.32ms      0.85  offset.OffsetDatetimeIndexArithmetic.time_add_offset(<SemiMonthBegin: day_of_month=15>)
-    2.97ms     2.52ms      0.85  offset.OffsetDatetimeIndexArithmetic.time_add_offset(<SemiMonthEnd: day_of_month=15>)
-   14.30μs    12.11μs      0.85  offset.OffestDatetimeArithmetic.time_apply_np_dt64(<YearEnd: month=12>)
-  659.15μs   554.42μs      0.84  stat_ops.Correlation.time_corr_series('kendall', False)
-   14.27μs    11.97μs      0.84  offset.OffestDatetimeArithmetic.time_apply(<MonthEnd>)
-   28.20μs    23.62μs      0.84  timestamp.TimestampProperties.time_month_name(<DstTzInfo 'Europe/Amsterdam' LMT+0:20:00 STD>, None)
-    2.72ms     2.27ms      0.83  offset.OffsetSeriesArithmetic.time_add_offset(<BusinessDay>)
-   15.13μs    12.54μs      0.83  offset.OffestDatetimeArithmetic.time_apply(<YearBegin: month=1>)
-    1.46ms     1.21ms      0.83  inference.NumericInferOps.time_divide(<class 'numpy.float32'>)
-   23.82μs    19.66μs      0.83  offset.OffestDatetimeArithmetic.time_subtract(<QuarterEnd: startingMonth=3>)
-    2.49ms     2.05ms      0.82  offset.OffsetDatetimeIndexArithmetic.time_add_offset(<BusinessDay>)
-   20.54ms    16.83ms      0.82  gil.ParallelReadCSV.time_read_csv('object')
-    2.90ms     2.10ms      0.73  binary_ops.Ops2.time_frame_series_dot
-  263.60μs   177.22μs      0.67  timestamp.TimestampOps.time_ceil('US/Eastern')
-  235.78μs   155.93μs      0.66  period.PeriodConstructor.time_period_constructor('D', True)
-  257.55μs   168.09μs      0.65  timestamp.TimestampOps.time_floor('US/Eastern')
-  237.40μs   153.90μs      0.65  period.PeriodConstructor.time_period_constructor('D', False)
-  175.60μs   105.99μs      0.60  timestamp.TimestampOps.time_ceil(<UTC>)
-  177.86μs   104.15μs      0.59  timestamp.TimestampOps.time_ceil(tzutc())
-  176.84μs   100.10μs      0.57  timestamp.TimestampOps.time_floor(tzutc())
-  180.45μs   101.56μs      0.56  timestamp.TimestampOps.time_floor(<UTC>)
-  153.79μs    85.77μs      0.56  timestamp.TimestampOps.time_floor(None)
-  312.88μs   170.21μs      0.54  period.PeriodProperties.time_property('min', 'end_time')
-  156.10μs    84.35μs      0.54  timestamp.TimestampOps.time_ceil(None)
-  145.06μs    76.65μs      0.53  period.PeriodProperties.time_property('M', 'end_time')
-  132.68μs    68.85μs      0.52  period.PeriodProperties.time_property('M', 'start_time')
-  134.91μs    69.82μs      0.52  period.PeriodProperties.time_property('min', 'start_time')
-  130.99μs    67.64μs      0.52  period.PeriodUnaryMethods.time_to_timestamp('M')
-  138.79μs    66.86μs      0.48  period.PeriodUnaryMethods.time_to_timestamp('min')
-  123.06μs    59.20μs      0.48  period.PeriodUnaryMethods.time_now('min')
-   79.34μs    21.58μs      0.27  period.PeriodUnaryMethods.time_asfreq('M')
-   78.83μs    20.22μs      0.26  period.PeriodUnaryMethods.time_asfreq('min')
-   79.68μs    17.13μs      0.21  period.PeriodUnaryMethods.time_now('M')
```
Closing as concept-proved.  Will revisit after more of offsets id moved up to liboffsets.