BUG: sql different offsets (1/2 modify infer_dtype)
- [x] closes #30207
- [x] tests added / passed
- [x] passes `black pandas`
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry

# Explanations

For to_sql no errors are raised anymore when we have Series containing datetimes with timezone and different offsets (unlike in GH30207) and the data is saved as expected (I tested with postgres which does not save offsets anyways).

Concerning read_sql however there are several points to discuss:

1. if we want to have datetime64 instead of "object" (as described in [GH30207](https://github.com/pandas-dev/pandas/issues/30207)) we lose the offsets. So this would be a breaking change and would require first a warning in the next version then we implement it in the version after, right?

2. is this patch I made acceptable?

The probem is that read_sql uses pd.DataFrame.from_records and it reads datetimes with different offsets as "object" instead of datetime64[ns,...] (e.g. datetime64[ns, psycopg2.tz.FixedOffsetTimezone(offset=60, name=None)]).

If we wanted to modify this behavior we would have to modify the C extension _libs.lib. I don't know how to help with that and I am not sure we want to do this. Even if we could pack the datetimes with differents offsets into a Series with a datetime64 dtype the methods of the .dt accessor are not compatible anyways: currently a ValueError is thrown if one attempts to use the accessor in this case (see traceback in the example in "Before PR" below).



# Before PR


```python
import datetime
import psycopg2
import sqlalchemy
import pandas as pd
from pandas.io.sql import read_sql, to_sql

# CREATE ENGINE
# engine = sqlalchemy.create_engine("CONNECTION_STRING")

# create datetimes with timezone that have different offsets
data = [[datetime.datetime(2019, 11, 14, 16, 12, 0, tzinfo=psycopg2.tz.FixedOffsetTimezone(offset=60, name=None))],
        [datetime.datetime(2019, 8, 7, 15, 37, 4, tzinfo=psycopg2.tz.FixedOffsetTimezone(offset=120, name=None))]]

df = pd.DataFrame(data, columns = ['ts_tz'])

# save data
to_sql(frame = df, 
       name = 'timezone_diff_offsets_test',
       con = engine,
       schema = 'public',
       if_exists = 'replace',
       index = False)
```

<details>
 <summary>Traceback</summary>


```python-traceback

    ---------------------------------------------------------------------------

    ValueError                                Traceback (most recent call last)

    ~/GitHub/pandas/pandas/core/arrays/datetimes.py in objects_to_datetime64ns(data, dayfirst, yearfirst, utc, errors, require_iso8601, allow_object)
       1968         try:
    -> 1969             values, tz_parsed = conversion.datetime_to_datetime64(data)
       1970             # If tzaware, these values represent unix timestamps, so we


    ~/GitHub/pandas/pandas/_libs/tslibs/conversion.pyx in pandas._libs.tslibs.conversion.datetime_to_datetime64()


    ValueError: Array must be all same time zone

    
    During handling of the above exception, another exception occurred:


    ValueError                                Traceback (most recent call last)

    <ipython-input-2-a66775d04fe3> in <module>
         20        schema = 'public',
         21        if_exists = 'replace',
    ---> 22        index = False)
    

    ~/GitHub/pandas/pandas/io/sql.py in to_sql(frame, name, con, schema, if_exists, index, index_label, chunksize, dtype, method)
        519         chunksize=chunksize,
        520         dtype=dtype,
    --> 521         method=method,
        522     )
        523 


    ~/GitHub/pandas/pandas/io/sql.py in to_sql(self, frame, name, if_exists, index, index_label, schema, chunksize, dtype, method)
       1318             index_label=index_label,
       1319             schema=schema,
    -> 1320             dtype=dtype,
       1321         )
       1322         table.create()


    ~/GitHub/pandas/pandas/io/sql.py in __init__(self, name, pandas_sql_engine, frame, index, if_exists, prefix, index_label, schema, keys, dtype)
        620         if frame is not None:
        621             # We want to initialize based on a dataframe
    --> 622             self.table = self._create_table_setup()
        623         else:
        624             # no data provided, read-only mode


    ~/GitHub/pandas/pandas/io/sql.py in _create_table_setup(self)
        869         from sqlalchemy import Table, Column, PrimaryKeyConstraint
        870 
    --> 871         column_names_and_types = self._get_column_names_and_types(self._sqlalchemy_type)
        872 
        873         columns = [


    ~/GitHub/pandas/pandas/io/sql.py in _get_column_names_and_types(self, dtype_mapper)
        861         column_names_and_types += [
        862             (str(self.frame.columns[i]), dtype_mapper(self.frame.iloc[:, i]), False)
    --> 863             for i in range(len(self.frame.columns))
        864         ]
        865 


    ~/GitHub/pandas/pandas/io/sql.py in <listcomp>(.0)
        861         column_names_and_types += [
        862             (str(self.frame.columns[i]), dtype_mapper(self.frame.iloc[:, i]), False)
    --> 863             for i in range(len(self.frame.columns))
        864         ]
        865 


    ~/GitHub/pandas/pandas/io/sql.py in _sqlalchemy_type(self, col)
        971             # timezone information
        972             try:
    --> 973                 if col.dt.tz is not None:
        974                     return TIMESTAMP(timezone=True)
        975             except AttributeError:


    ~/GitHub/pandas/pandas/core/accessor.py in _getter(self)
         83         def _create_delegator_property(name):
         84             def _getter(self):
    ---> 85                 return self._delegate_property_get(name)
         86 
         87             def _setter(self, new_values):


    ~/GitHub/pandas/pandas/core/indexes/accessors.py in _delegate_property_get(self, name)
         60         from pandas import Series
         61 
    ---> 62         values = self._get_values()
         63 
         64         result = getattr(values, name)


    ~/GitHub/pandas/pandas/core/indexes/accessors.py in _get_values(self)
         51                 return PeriodArray(data, copy=False)
         52             if is_datetime_arraylike(data):
    ---> 53                 return DatetimeIndex(data, copy=False, name=self.name)
         54 
         55         raise TypeError(


    ~/GitHub/pandas/pandas/core/indexes/datetimes.py in __new__(cls, data, freq, tz, normalize, closed, ambiguous, dayfirst, yearfirst, dtype, copy, name)
        266             dayfirst=dayfirst,
        267             yearfirst=yearfirst,
    --> 268             ambiguous=ambiguous,
        269         )
        270 


    ~/GitHub/pandas/pandas/core/arrays/datetimes.py in _from_sequence(cls, data, dtype, copy, tz, freq, dayfirst, yearfirst, ambiguous)
        418             dayfirst=dayfirst,
        419             yearfirst=yearfirst,
    --> 420             ambiguous=ambiguous,
        421         )
        422 


    ~/GitHub/pandas/pandas/core/arrays/datetimes.py in sequence_to_dt64ns(data, dtype, copy, tz, dayfirst, yearfirst, ambiguous)
       1864             #  or M8[ns] to denote wall times
       1865             data, inferred_tz = objects_to_datetime64ns(
    -> 1866                 data, dayfirst=dayfirst, yearfirst=yearfirst
       1867             )
       1868             tz = maybe_infer_tz(tz, inferred_tz)


    ~/GitHub/pandas/pandas/core/arrays/datetimes.py in objects_to_datetime64ns(data, dayfirst, yearfirst, utc, errors, require_iso8601, allow_object)
       1972             return values.view("i8"), tz_parsed
       1973         except (ValueError, TypeError):
    -> 1974             raise e
       1975 
       1976     if tz_parsed is not None:


    ~/GitHub/pandas/pandas/core/arrays/datetimes.py in objects_to_datetime64ns(data, dayfirst, yearfirst, utc, errors, require_iso8601, allow_object)
       1963             dayfirst=dayfirst,
       1964             yearfirst=yearfirst,
    -> 1965             require_iso8601=require_iso8601,
       1966         )
       1967     except ValueError as e:


    ~/GitHub/pandas/pandas/_libs/tslib.pyx in pandas._libs.tslib.array_to_datetime()


    ~/GitHub/pandas/pandas/_libs/tslib.pyx in pandas._libs.tslib.array_to_datetime()


    ValueError: Tz-aware datetime.datetime cannot be converted to datetime64 unless utc=True


```

</details>


```python
# save datetimes with UTC timezone in different periods of the year
## create test data
data = [[datetime.datetime(2019, 11, 14, 15, 12, 0, tzinfo = datetime.timezone.utc)],
        [datetime.datetime(2019, 8, 7, 13, 37, 4,  tzinfo = datetime.timezone.utc)]]

df = pd.DataFrame(data, columns = ['ts_tz'])

## save test data
to_sql(frame = df, 
       name = 'timezone_diff_offsets_test',
       con = engine,
       schema = 'public',
       if_exists = 'replace',
       index = False)

# upon reading if you have for instance "Europe/Paris"
# configured as timezone on your local machine then you get datetimes with timezone
# that have different offsets (because they are in different periods of the year)
# due to this you have "object" dtype and not datetime64[...]
df = read_sql('SELECT * FROM timezone_diff_offsets_test', engine)
print(df,'\n')
print(df.dtypes)
```

                           ts_tz
    0  2019-11-14 16:12:00+01:00
    1  2019-08-07 15:37:04+02:00 
    
    ts_tz    object
    dtype: object


# After PR


```python
import datetime
import psycopg2
import sqlalchemy
import pandas as pd
from pandas.io.sql import read_sql, to_sql

# CREATE ENGINE
# engine = sqlalchemy.create_engine("CONNECTION_STRING")

# create datetimes with timezone that have different offsets
data = [[datetime.datetime(2019, 11, 14, 16, 12, 0, tzinfo=psycopg2.tz.FixedOffsetTimezone(offset=60, name=None))],
        [datetime.datetime(2019, 8, 7, 15, 37, 4, tzinfo=psycopg2.tz.FixedOffsetTimezone(offset=120, name=None))]]

df = pd.DataFrame(data, columns = ['ts_tz'])

# save data - no exceptions occur
to_sql(frame = df, 
       name = 'timezone_diff_offsets_test',
       con = engine,
       schema = 'public',
       if_exists = 'replace',
       index = False)

# read data - offsets are dropped and dtype of column ts_tz is datetime64[ns, UTC]
df = read_sql('SELECT * FROM timezone_diff_offsets_test', engine)
print(df,'\n')
print(df.dtypes)
```

                          ts_tz
    0 2019-11-14 15:12:00+00:00
    1 2019-08-07 13:37:04+00:00 
    
    ts_tz    datetime64[ns, UTC]
    dtype: object

