@@ -279,7 +279,7 @@ class Datetime():
279279
280280 def __init__ (self , data = None , * , timestamp = None , year = None , month = None ,
281281 day = None , hour = None , minute = None , sec = None , nsec = None ,
282- tzoffset = 0 , tz = '' ):
282+ tzoffset = 0 , tz = '' , timestamp_since_utc_epoch = False ):
283283 """
284284 :param data: MessagePack binary data to decode. If provided,
285285 all other parameters are ignored.
@@ -294,7 +294,10 @@ def __init__(self, data=None, *, timestamp=None, year=None, month=None,
294294 :paramref:`~tarantool.Datetime.params.minute`,
295295 :paramref:`~tarantool.Datetime.params.sec`.
296296 If :paramref:`~tarantool.Datetime.params.nsec` is provided,
297- it must be :obj:`int`.
297+ it must be :obj:`int`. Refer to
298+ :paramref:`~tarantool.Datetime.params.timestamp_since_utc_epoch`
299+ to clarify how timezone-aware datetime is computed from
300+ the timestamp.
298301 :type timestamp: :obj:`float` or :obj:`int`, optional
299302
300303 :param year: Datetime year value. Must be a valid
@@ -344,8 +347,60 @@ def __init__(self, data=None, *, timestamp=None, year=None, month=None,
344347 :param tz: Timezone name from Olson timezone database.
345348 :type tz: :obj:`str`, optional
346349
350+ :param timestamp_since_utc_epoch: Parameter to set timestamp
351+ convertion behavior for timezone-aware datetimes.
352+
353+ If ``False`` (default), behaves similar to Tarantool
354+ `datetime.new()`_:
355+
356+ .. code-block:: python
357+
358+ >>> dt = tarantool.Datetime(timestamp=1640995200, timestamp_since_utc_epoch=False)
359+ >>> dt
360+ datetime: Timestamp('2022-01-01 00:00:00'), tz: ""
361+ >>> dt.timestamp
362+ 1640995200.0
363+ >>> dt = tarantool.Datetime(timestamp=1640995200, tz='Europe/Moscow',
364+ ... timestamp_since_utc_epoch=False)
365+ >>> dt
366+ datetime: Timestamp('2022-01-01 00:00:00+0300', tz='Europe/Moscow'), tz: "Europe/Moscow"
367+ >>> dt.timestamp
368+ 1640984400.0
369+
370+ Thus, if ``False``, datetime is computed from timestamp
371+ since epoch and then timezone is applied without any
372+ convertion. In that case,
373+ :attr:`~tarantool.Datetime.timestamp` won't be equal to
374+ initialization
375+ :paramref:`~tarantool.Datetime.params.timestamp` for all
376+ timezones with non-zero offset.
377+
378+ If ``True``, behaves similar to :class:`pandas.Timestamp`:
379+
380+ .. code-block:: python
381+
382+ >>> dt = tarantool.Datetime(timestamp=1640995200, timestamp_since_utc_epoch=True)
383+ >>> dt
384+ datetime: Timestamp('2022-01-01 00:00:00'), tz: ""
385+ >>> dt.timestamp
386+ 1640995200.0
387+ >>> dt = tarantool.Datetime(timestamp=1640995200, tz='Europe/Moscow',
388+ ... timestamp_since_utc_epoch=True)
389+ >>> dt
390+ datetime: Timestamp('2022-01-01 03:00:00+0300', tz='Europe/Moscow'), tz: "Europe/Moscow"
391+ >>> dt.timestamp
392+ 1640995200.0
393+
394+ Thus, if ``True``, datetime is computed in a way that
395+ :attr:`~tarantool.Datetime.timestamp` will always be equal
396+ to initialization
397+ :paramref:`~tarantool.Datetime.params.timestamp`.
398+ :type timestamp_since_utc_epoch: :obj:`bool`, optional
399+
347400 :raise: :exc:`ValueError`, :exc:`~tarantool.error.MsgpackError`,
348401 :class:`pandas.Timestamp` exceptions
402+
403+ .. _datetime.new(): https://www.tarantool.io/en/doc/latest/reference/reference_lua/datetime/new/
349404 """
350405
351406 if data is not None :
@@ -358,6 +413,16 @@ def __init__(self, data=None, *, timestamp=None, year=None, month=None,
358413 self ._tz = tz
359414 return
360415
416+ tzinfo = None
417+ if tz != '' :
418+ if tz not in tt_timezones .timezoneToIndex :
419+ raise ValueError (f'Unknown Tarantool timezone "{ tz } "' )
420+
421+ tzinfo = get_python_tzinfo (tz , ValueError )
422+ elif tzoffset != 0 :
423+ tzinfo = pytz .FixedOffset (tzoffset )
424+ self ._tz = tz
425+
361426 # The logic is same as in Tarantool, refer to datetime API.
362427 # https://www.tarantool.io/en/doc/latest/reference/reference_lua/datetime/new/
363428 if timestamp is not None :
@@ -375,6 +440,11 @@ def __init__(self, data=None, *, timestamp=None, year=None, month=None,
375440 datetime = pandas .to_datetime (total_nsec , unit = 'ns' )
376441 else :
377442 datetime = pandas .to_datetime (timestamp , unit = 's' )
443+
444+ if not timestamp_since_utc_epoch :
445+ self ._datetime = datetime .replace (tzinfo = tzinfo )
446+ else :
447+ self ._datetime = datetime .replace (tzinfo = pytz .UTC ).tz_convert (tzinfo )
378448 else :
379449 if nsec is not None :
380450 microsecond = nsec // NSEC_IN_MKSEC
@@ -383,25 +453,11 @@ def __init__(self, data=None, *, timestamp=None, year=None, month=None,
383453 microsecond = 0
384454 nanosecond = 0
385455
386- datetime = pandas .Timestamp (year = year , month = month , day = day ,
387- hour = hour , minute = minute , second = sec ,
388- microsecond = microsecond ,
389- nanosecond = nanosecond )
390-
391- if tz != '' :
392- if tz not in tt_timezones .timezoneToIndex :
393- raise ValueError (f'Unknown Tarantool timezone "{ tz } "' )
394-
395- tzinfo = get_python_tzinfo (tz , ValueError )
396- self ._datetime = datetime .replace (tzinfo = tzinfo )
397- self ._tz = tz
398- elif tzoffset != 0 :
399- tzinfo = pytz .FixedOffset (tzoffset )
400- self ._datetime = datetime .replace (tzinfo = tzinfo )
401- self ._tz = ''
402- else :
403- self ._datetime = datetime
404- self ._tz = ''
456+ self ._datetime = pandas .Timestamp (
457+ year = year , month = month , day = day ,
458+ hour = hour , minute = minute , second = sec ,
459+ microsecond = microsecond ,
460+ nanosecond = nanosecond , tzinfo = tzinfo )
405461
406462 def _interval_operation (self , other , sign = 1 ):
407463 """
0 commit comments