@@ -279,7 +279,7 @@ class Datetime():
279
279
280
280
def __init__ (self , data = None , * , timestamp = None , year = None , month = None ,
281
281
day = None , hour = None , minute = None , sec = None , nsec = None ,
282
- tzoffset = 0 , tz = '' ):
282
+ tzoffset = 0 , tz = '' , timestamp_since_utc_epoch = False ):
283
283
"""
284
284
:param data: MessagePack binary data to decode. If provided,
285
285
all other parameters are ignored.
@@ -294,7 +294,10 @@ def __init__(self, data=None, *, timestamp=None, year=None, month=None,
294
294
:paramref:`~tarantool.Datetime.params.minute`,
295
295
:paramref:`~tarantool.Datetime.params.sec`.
296
296
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.
298
301
:type timestamp: :obj:`float` or :obj:`int`, optional
299
302
300
303
:param year: Datetime year value. Must be a valid
@@ -344,8 +347,60 @@ def __init__(self, data=None, *, timestamp=None, year=None, month=None,
344
347
:param tz: Timezone name from Olson timezone database.
345
348
:type tz: :obj:`str`, optional
346
349
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
+
347
400
:raise: :exc:`ValueError`, :exc:`~tarantool.error.MsgpackError`,
348
401
:class:`pandas.Timestamp` exceptions
402
+
403
+ .. _datetime.new(): https://www.tarantool.io/en/doc/latest/reference/reference_lua/datetime/new/
349
404
"""
350
405
351
406
if data is not None :
@@ -358,6 +413,16 @@ def __init__(self, data=None, *, timestamp=None, year=None, month=None,
358
413
self ._tz = tz
359
414
return
360
415
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
+
361
426
# The logic is same as in Tarantool, refer to datetime API.
362
427
# https://www.tarantool.io/en/doc/latest/reference/reference_lua/datetime/new/
363
428
if timestamp is not None :
@@ -375,6 +440,11 @@ def __init__(self, data=None, *, timestamp=None, year=None, month=None,
375
440
datetime = pandas .to_datetime (total_nsec , unit = 'ns' )
376
441
else :
377
442
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 )
378
448
else :
379
449
if nsec is not None :
380
450
microsecond = nsec // NSEC_IN_MKSEC
@@ -383,25 +453,11 @@ def __init__(self, data=None, *, timestamp=None, year=None, month=None,
383
453
microsecond = 0
384
454
nanosecond = 0
385
455
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 )
405
461
406
462
def _interval_operation (self , other , sign = 1 ):
407
463
"""
0 commit comments