AppleGrew
AppleGrew

Reputation: 9570

How to create tzinfo when I have UTC offset?

I have one timezone's offset from UTC in seconds (19800) and also have it in string format - +0530.

How do I use them to create a tzinfo instance? I looked into pytz, but there I could only find APIs that take timezone name as input.

Upvotes: 59

Views: 82069

Answers (7)

Turtles Are Cute
Turtles Are Cute

Reputation: 3426

With Python 3.2 or higher, you can do this using the builtin datetime library:

import datetime
datetime.timezone(-datetime.timedelta(hours=5, minutes=30))

To solve your specific problem, you could use this regex pattern:

sign, hours, minutes = re.match('([+\-]?)(\d{2})(\d{2})', '+0530').groups()
sign = -1 if sign == '-' else 1
hours, minutes = int(hours), int(minutes)

tzinfo = datetime.timezone(sign * datetime.timedelta(hours=hours, minutes=minutes))
datetime.datetime(2013, 2, 3, 9, 45, tzinfo=tzinfo)

Upvotes: 53

nigani
nigani

Reputation: 338

It's simple, only import datetime

>>> tz = datetime.timezone(datetime.timedelta(seconds=19800))

Next, you can, for example

>>> datetime.datetime.now(tz).isoformat(timespec='minutes')
'2021-08-03T18:07+05:30'

Upvotes: 3

Jim At Your Service
Jim At Your Service

Reputation: 1

Based on the excellent answer from @Joe here, I wrote a function which monkey-patches pytz to support named timezones such as 'UTC-06:00' or 'UTC+11:30'. I can construct one of these names based on an offset sent to me from a browser, which only has an integer given by Javascript new Date().getTimezoneOffset() as described here and referenced here, and then I can post the name as a normal timezone name usable by the rest of my application which uses pytz.

This mechanism would also work for the op in this question who has an offset in seconds.

Example construct tzname using the offset the op has in this question:

minutes = offset // 60
tzname = 'UTC%s%02d:%02d' % (
    '-' if minutes < 0 else '+',
    abs(minutes) // 60, abs(minutes) % 60))

Example construct tzname using a browser timezone offset returned by JavaScript new Date().getTimezoneOffset(), which of note has a reversed sign:

tzname = (
    'UTC%s%02d:%02d' % (
        '-' if browser_tz_offset > 0 else '+', # reverse sign
        abs(browser_tz_offset) // 60, abs(browser_tz_offset) % 60))

Use the named zone to construct a tzinfo object:

from datetime import datetime
import pytz
tz = pytz.timezone(tzname)  # tzname = e.g. 'UTC-06:00' or 'Europe/Madrid'
localized_now = datetime.now(tz)

I call this function during application startup.

import re
import pytz
from dateutil import tz as dateutil_tz

def post_load_pytz_offset_timezones_server_wide():

    pristine_pytz_timezone = pytz.timezone

    def extended_pytz_timezone(zone):
        matches = re.match('^UTC([+-])([0-9][0-9]):([0-9][0-9])$', zone) if zone else None
        if matches:
            sign = -1 if matches.group(1) == '-' else 1
            minutes = int(matches.group(2)) * 60 + int(matches.group(3))
            tzinfo = dateutil_tz.tzoffset(zone, sign*minutes*60)
        else:
            tzinfo = pristine_pytz_timezone(zone)
    return tzinfo

    pytz.timezone = extended_pytz_timezone

Upvotes: 0

kubilayeksioglu
kubilayeksioglu

Reputation: 498

If you have pytz:

tz = pytz.FixedOffset(180)
now = timezone.now()
local_now = tz.normalize(now.astimezone(tz))

Upvotes: 12

Joe
Joe

Reputation: 16831

If you can, take a look at the excellent dateutil package instead of implementing this yourself.

Specifically, tzoffset. It's a fixed offset tzinfo instance initialized with offset, given in seconds, which is what you're looking for.

Update

Here's an example. Be sure to run pip install python-dateutil first.

from datetime import datetime
from dateutil import tz

# First create the tzinfo object
tzlocal = tz.tzoffset('IST', 19800)

# Now add it to a naive datetime
local = naive.replace(tzinfo=tzlocal)

# Or convert another timezone to it
utcnow = datetime.utcnow().replace(tzinfo=tz.tzutc())
now = utcnow.astimezone(tzlocal)

I looked up the name IST from here. The name can really be anything. Just be careful if you deviate, since if you use code that relies on the name, it could lead to bugs later on.

By the way, if you have the timezone name upfront, and your operating system supports it, you can use gettz instead:

# Replace the above with this
tzlocal = tz.gettz('IST')

Upvotes: 38

Dmitry Nedbaylo
Dmitry Nedbaylo

Reputation: 2334

You have to implement subclass of datetime.tzinfo class. General guide is described here, where you also can find excellent examples of custom tzinfo implementations.

Here is example (given that there is no daylight saving time) :

from datetime import tzinfo, timedelta, datetime
from pytz import UTC


class MyUTCOffsetTimezone (tzinfo):

    def __init__(self, offset=19800, name=None):
        self.offset = timedelta(seconds=offset)
        self.name = name or self.__class__.__name__

    def utcoffset(self, dt):
        return self.offset

    def tzname(self, dt):
        return self.name

    def dst(self, dt):
        return timedelta(0)


now = datetime.now(tz=UTC)
print now
# -> 2015-01-28 10:46:42.256841+00:00
print now.astimezone(MyUTCOffsetTimezone())
# -> 2015-01-28 16:16:42.256841+05:30
print datetime.now(MyUTCOffsetTimezone())
# -> 2015-01-28 16:16:42.256915+05:30

Upvotes: 11

Serge Ballesta
Serge Ballesta

Reputation: 148890

Python Standard Library (8.1.6) says that :

  • tzinfo is an abstract base class
  • the datetime module does not supply any concrete subclasses of tzinfo
  • you need to derive a concrete subclass, and (at least) supply implementations of the standard tzinfo methods needed by the datetime methods you use
  • a concrete subclass of tzinfo may need to implement the following methods ... If in doubt, simply implement all of them
    • tzinfo.utcoffset(self, dt) : return offset of local time from UTC, in minutes east of UTC
    • tzinfo.dst(self, dt) : return the daylight saving time (DST) adjustment, in minutes east of UTC, or None if DST information isn’t known
    • tzinfo.tzname(self, dt) : return the time zone name corresponding to the datetime object dt, as a string

All that means that you will have to provide your own implementation for the tzinfo. For example :

class UTC0530(datetime.tzinfo):
    """tzinfo derived concrete class named "+0530" with offset of 19800"""
    # can be configured here
    _offset = datetime.timedelta(seconds = 19800)
    _dst = datetime.timedelta(0)
    _name = "+0530"
    def utcoffset(self, dt):
        return self.__class__._offset
    def dst(self, dt):
        return self.__class__._dst
    def tzname(self, dt):
        return self.__class__._name

Usage :

tz = UTC0530()
d = datetime.datetime.now(tz)
d.isoformat()

output :

2015-01-27T20:19:41.257000+05:30

Upvotes: 22

Related Questions