Timezone.py :  » Database » PyDO » skunkweb-3.4.4 » pylibs » Date » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Database » PyDO 
PyDO » skunkweb 3.4.4 » pylibs » Date » Timezone.py
#  
#  Copyright (C) 2001 Andrew T. Csillag <drew_csillag@geocities.com>
#  
#      You may distribute under the terms of either the GNU General
#      Public License or the SkunkWeb License, as specified in the
#      README file.
#   
"""
This module provides "timezone" objects 
which the Date module can use
when converting DateTime objects from one
time zone to another.

Timezone objects are used by Date.Convert 
to convert date/times between different timezones on Earth. 
Computers have had many different ways to specify 
timezone values; our Date module tries to 
accommodate them all.

First of all, let's start with the simplest 
kind of timezone, the offset: a number of 
hours/minutes ahead or behind of Universal Coordinated Time 
(UTC/GMT). These kinds of timezones are specified as strings, 
with the number of hours-then-minutes and a "-" for behind 
of UTC and a "+" (or nothing) for ahead of UTC:

"-0400" - 4 hours behind UTC
"+0100" - 1 hour ahead of UTC
"0230" - 2 hours, 30 minutes ahead of UTC
"-0000" or "+0000" - UTC

You can use strings like this with 
Date.Convert:

Date.Convert(date, to_zone="-0400", from_zone="-0000")

will add four hours to the DateTime object you pass in.

The other kind of timezone is the timezone object. 
It's not just an offset from UTC; it represents a set 
of rules on when to use daylight saving time and when not 
to use it. These timezones are actually Python objects, 
with methods available to determine if a date/time 
is in daylight savings or not, etc.

These timezone objects can be used directly, but it's more
common to specify them to Date.Convert by their name 
strings. A timezone object has many name strings, which
try to provide easy and expected names for the timezone
in all of the languages and naming systems we use. For example,
the timezone object for United States Eastern Time can be
identified by the following names:

'ET'
'Eastern'
'US/Eastern'
'USA Eastern'
'EEUU Este' 
'EUA Leste'
'us.fl'
'us.ny'

There is one "special" timezone name, and that is "LOCAL". 
If you specify the string "LOCAL" as the timezone, 
the Date module takes that to mean the machine's local 
timezone. (This may not be the same as your own personal 
local timezone, so be aware of which timezone your 
machine is set to.) "LOCAL" identifies a special LocalTimezone
object in the Timezone module, which handles conversions
gracefully to and from local machine time.

Daylight Savings Time

This module's timezone objects do support daylight savings
time, with some important restrictions. Daylight savings "rules"
are implemented in each TimezoneDST object, but there's only one set
of rules in each object, the most recent rules adopted by the timezone.
In the United States, the rules have been the same since the 1960s;
so the US-based timezone objects will handle daylight savings correctly
for any DateTime objects more recent than 1960 or so. Other timezones
have made recent changes to their rules or, worse yet, they change
their rules every year!

What this means to you, the practical programmer, is that 
this module guarantees correct daylight savings behavior only
for the current date/time on the machine. For other times, correct
behavior depends on when the timezone's rules have changed. Here is
a short guide to "daylight savings dependability" for different timezones:

USA timezones: all dates from Jan 1, 1970
Mexico timezones: all dates from Jan 1, 1990
Brazil timezones: all dates from Jan 1, 1998
Other Latin American timezones: all dates from Jan 1, 2000
"""

import types
try:
    from mx.DateTime import DateTimeDelta,RelativeDateTime,utc2local,local2utc,oneHour
except:
    from DateTime import DateTimeDelta,RelativeDateTime,utc2local,local2utc,oneHour
    
# "offset" handling functions
#
# offsets are the strings like "-0400" that are used
# in ISO date formats.
#
# It's not worth making Timezone objects for these,
# so we're using a regex instead, and Convert() does
# the heavy lifting.


import re
#from DateTime import oneHour

_offset_regex = re.compile(r'([+-]?)([01][0-9]|2[0-3])([0-5][0-9])')

# helper function for Convert, which takes the parts
# of the matched offset regex and makes a delta

def _make_offset_delta(tup):
    delta = DateTimeDelta(0, int(tup[1]), int(tup[2]))
    if tup[0] == '-': delta =  -1 * delta
    return delta

# Conversion function
#
# to_zone and from_zone accept either strings or 
# Timezone instances. IF strings, it checks if string
# is a GMT offset like '-0400'. If so, it handles it
# as a falt offset from GMT. If not, it looks it up
# with get_timezone(). get_timezone will throw
# a ValueError if it can't find the timezone at that point.
# 'LOCAL' will always find the special LocalTimezone object.

def Convert(date, to_zone='LOCAL', from_zone='LOCAL'):
    """
    Accepts a DateTime object in date, 
    converts it from the timezone indicated in 
    from_zone to the timezone indicated 
    in to_zone, and returns the converted 
    DateTime object. Note that from_zone 
    and to_zone default to the local timezone. 
    If you do this:

    Date.Convert(date)
    
    then no conversion will take place, and you will 
    receive the DateTime object unchanged.

    from_zone and to_zone 
    accept "timezone values" (see below), 
    which are any values that can accurately 
    describe a timezone. Note the special timezone values:

    "UTC" or "GMT": the UTC timezone
    "LOCAL": the local timezone
    
    Please read the Timezone documentation 
    about timezone objects!
    """
    # check if from_zone is an offset string
    # if so, set the conversion func to conv_from_offset.
    if type(from_zone) == types.StringType:
  mt =  _offset_regex.match(from_zone)
  if mt:
      first = date - _make_offset_delta(mt.groups())
  else:
      from_zone = get_timezone(from_zone)
      first = from_zone.to_utc(date)
    else: first = from_zone.to_utc(date)

    if type(to_zone) == types.StringType:
  mt =  _offset_regex.match(to_zone)
  if mt:
      second = first + _make_offset_delta(mt.groups())
  else:
      to_zone = get_timezone(to_zone)
      second = to_zone.from_utc(first)
    else: second = to_zone.from_utc(first)

    return second

# Daylight check function

def isDST(date, zone='LOCAL'):
    """
    Given a DateTime object in date, 
    and a timezone name, offset, or object in 
    zone, returns 1 or 0, whether 
    the date falls in daylight saving time 
    for that timezone.
    """
    if type(zone) == types.StringType:
  zone = get_timezone(zone)
    return zone.localdate_is_dst(date)

isDaylight = isDST

# timezone lookup dictionary

_tzdict = {}

# timezone lookup function
#
# Timezone objects aren't put in the module namespace,
# but are instead indexed bt strings in _tzdict.
# get_timezone(name) queries _tzdict, and raises
# its own, more meaningful error to the user if tz doesn't exist.

def get_timezone(name):
    """
    Given a timezone name, returns the corresponding
    Timezone object. If no object is available
    under this name, a ValueError is raised.
    """
    if not _tzdict.has_key(name):
  raise ValueError, "Timezone %s not valid" % repr(name)
    return _tzdict[name]

# timezone factory 
#
# instantiates Timezone objects and installs them in _tzdict.

def _make_timezone(klass, names, *dummy, **kwargs):
    tzobj = apply(klass, dummy, kwargs)
    for n in names: _tzdict[n] = tzobj

# ------------------------------------------------------
# TIMEZONE CLASSES
#
# these classes normalize the structure and interface
# of timezones. 

class Timezone:
    """Represents a "basic" timezone which does not implement
       daylight saving time. It still provides daylight-checking
       methods, however, which return 0."""

    def __init__(self, *dummy, **kwargs):
        """
        Timezone objects should only be instantiated
        by the Timezone module itself. Use the
        get_timezone function to get
        timezone objects.
        """
  for k, v in kwargs.items(): self.__dict__[k] = v

    def __setattr__(self, item, value):
  raise AttributeError, "Timezone objects are immutable"

    # attributes of any Timezone instance are not
    # publicly accessible. Use the method interface always.
    def __getattr__(self, item):
  raise AttributeError, item

    # timezones are equivalent iff their instance
    # dictionary's items() are equivalent. This is 
    # a shortcut (though not really a hack) that
    # allows for future changes to the attribute namespace
    # of Timezone instances. By design, Timezone instance
    # attributes are restricted to objects which determine
    # the timezone settings, nothing else. Thus this cmp
    # method woks like a charm.

    def __cmp__(self, other):
  # must be another Timezone instance
  if not isinstance(other, Timezone):
      raise TypeError, \
      "Timezone object %s can only be compared to other Timezone objects" \
      % repr(self)
        # get both instance dicts items(), sorted.
  selfdict = self.__dict__.items()
  selfdict.sort()
  otherdict = other.__dict__.items()
  otherdict.sort()
  return cmp(selfdict, otherdict)

    def localdate_is_dst(self, date): 
        """
  Given a DateTime object, returns 1 or 0
  whether the date, if it were in the timezone
  in question, is daylight saving time.
  """
        return 0
    def utcdate_is_dst(self, date): 
  """
  Given a DateTime object, returns 1 or 0
  whether the date, expressed as a UTC date,
  would correspond to a date in this timezone
  which is daylight saving time.
  """
  return 0
    def find_offset(self, date): 
  """
  Returns a string with the offset from UTC
  for the DateTime object passed in date.
  Yes, this method obeys daylight saving time, and
  considers the date object passed to be expressed
  in the timezone in question.
  """
  return self.__dict__['offset']
    def from_utc(self, date): 
  """
  Given a DateTime object, it considers that
  date to be in UTC. Returns a DateTime object
  that is the UTC date converted to this timezone.
  """
  return date + self.__dict__['offset_delta']
    def to_utc(self, date): 
  """
  Given a DateTime object expressed as a date
  in this timezone, returns a DateTime object
  that is that date converted to UTC.
  """
  return date - self.__dict__['offset_delta']

class TimezoneDST(Timezone):
    """
    Represents a timezone which implements daylight 
    saving time. Has the same interface as the
    Timezone class.
    """

    def localdate_is_dst(self, date):
        """
  Given a DateTime object, returns 1 or 0
  whether the date, if it were in the timezone
  in question, is daylight saving time.
  """
  forward = date + self.__dict__['dst_forward'] + oneHour
  back = date + self.__dict__['dst_back'] - oneHour
  south = (forward > back)
  if south: return (date >= forward or date < back)
  else: return (date >= forward and date < back)

    def utcdate_is_dst(self, date):
  """
  Given a DateTime object, returns 1 or 0
  whether the date, expressed as a UTC date,
  would correspond to a date in this timezone
  which is daylight saving time.
  """
  forward = date - self.__dict__['offset_delta'] \
                  + self.__dict__['dst_forward'] #+ oneHour
  back = date - self.__dict__['dst_delta'] \
               + self.__dict__['dst_back'] #- oneHour
  # since we just subtracted offsets, 
  # the south rule is reversed
  south = (forward > back)
  if south: return (date >= forward or date < back)
  else: return (date >= forward and date < back)

    def find_offset(self, date):
  """
  Returns a string with the offset from UTC
  for the DateTime object passed in date.
  Yes, this method obeys daylight saving time, and
  considers the date object passed to be expressed
  in the timezone in question.
  """
  if self.localdate_is_dst(date): return self.__dict__['dst_offset']
  else: return self.__dict__['offset']

    def from_utc(self, date):
  """
  Given a DateTime object, it considers that
  date to be in UTC. Returns a DateTime object
  that is the UTC date converted to this timezone.
  """
  if not self.utcdate_is_dst(date):
      return date + self.__dict__['offset_delta']
        else: return date + self.__dict__['dst_delta']

    def to_utc(self, date):
  """
  Given a DateTime object expressed as a date
  in this timezone, returns a DateTime object
  that is that date converted to UTC.
  """
  if not self.localdate_is_dst(date):
      return date - self.__dict__['offset_delta']
        else: return date - self.__dict__['dst_delta']

class LocalTimezone(Timezone):
    """
    A special class, only to be instantiated once,
    which provides the Timezone structure and interface
    to the machine's local timezone. The methods
    merely pass through to localtime and other
    basic DateTime functions.

    The LocalTimezone instance will faithfully reflect
    changes that you make to the local timezone at 
    run-time, e.g. changing the TZ environment variable
    in Unix. Since the offset and dst_offset attributes
    are not directly accessible by the user, we don't
    have to set offset and dst_offset at __init__ time,
    as we do with all other Timezone instances.
    """

    def __init__(self, *ignore, **ignorekw): pass

    def localdate_is_dst(self, date):
        """
  Given a DateTime object, returns 1 or 0
  whether the date, if it were in the timezone
  in question, is daylight saving time.
  """
  # get the ticks, then run it back through localtime()
  t = date.ticks()
  r = localtime(t).dst
  # if local time is DST-ignorant, the answer is false
  if r != -1: return r
  else: return 0

    def utcdate_is_dst(self, date):
  """
  Given a DateTime object, returns 1 or 0
  whether the date, expressed as a UTC date,
  would correspond to a date in this timezone
  which is daylight saving time.
  """
  # convert utc to local
  return self.localdate_is_dst( utc2local(date) )

    def find_offset(self, date): 
  """
  Returns a string with the offset from UTC
  for the DateTime object passed in date.
  Yes, this method obeys daylight saving time, and
  considers the date object passed to be expressed
  in the timezone in question.
  """
  # (Is this really necessary?)
  t = date.ticks()
  r = localtime(t).gmtoffset()
  # format the sucker
  return "%+03d%02d" % (r.hour, r.minute)

    def from_utc(self, date): 
  """
  Given a DateTime object, it considers that
  date to be in UTC. Returns a DateTime object
  that is the UTC date converted to this timezone.
  """
  return utc2local(date)

    def to_utc(self, date): 
  """
  Given a DateTime object expressed as a date
  in this timezone, returns a DateTime object
  that is that date converted to UTC.
  """
  return local2utc(date)

# the special *local* timezone. It's not a real timezone object.
# Instead, if someone specifies 'LOCAL' as the timezone in question,
# the top level functions in Date.py (Convert, isDST) play games
# with DateTime.localtime and such. See the Date.py file for details.

_make_timezone(
    LocalTimezone, ('LOCAL',),
)

# -------------------------------------------------
# STANDARD (NON-DAYLIGHT) TIMEZONES
# 
# The following timezones do not implement daylight
# saving time. Thus they are Timezone instances,
# and not TimezoneDST instances.

# good old UTC
_make_timezone(
    Timezone, ('UTC', 'GMT', 'UCT', 'Universal', 'Greenwich'),
    offset='-0000',
    offset_delta=DateTimeDelta(0,0,0)
)

# Argentina and Uruguay (GMT-3)
_make_timezone(
    Timezone, 
    ('Argentina', 'ar',
     'Uruguay', 'Uruguai', 'uy', 
    ),
    offset='-0300',
    offset_delta=DateTimeDelta(0,0,-180)
)

# GMT-4 for LatAms
_make_timezone(
    Timezone, 
    ('Bolivia', 'Bol\355via', 'bo',
     'Venezuela', 've',
     'Puerto Rico', 'Porto Rico', 'pr',
     # oh what the hell
     'USVI', 'vi',
    # 2000.12.05: DR gov, in face of protests, repealed
    # daylight time after just two months in effect 
    # (biz guys hated it). Back to -0400 GMT year round.
    'Dominican Republic', 'Rep\372blica Dominicana', 'do',
    ),
    offset='-0400',
    offset_delta=DateTimeDelta(0,0,-240)
)

# GMT-5 for LatAms
_make_timezone(
    Timezone, 
    ('Colombia', 'Col\364mbia', 'co',
     'Ecuador', 'Equador', 'ec',
     'Panama', 'Panam\341', 'pa',
     'Peru', 'Per\372', 'pe',
    ),
    offset='-0500',
    offset_delta=DateTimeDelta(0,0,-300)
)

# GMT-6 for LatAms
_make_timezone(
    Timezone, 
    ('Nicaragua', 'Nic\341ragua', 'ni', 
     'Costa Rica', 'cr',
     'El Salvador', 'sv',
     'Guatemala', 'gt',
     'Honduras', 'hn',
    ),
    offset='-0600',
    offset_delta=DateTimeDelta(0,0,-360)
)

# USA Arizona 
#
# (dumb, but I'm from Arizona, so I get to include it)

_make_timezone(
    Timezone, ('USA Arizona', 'EEUU Arizona', 'EUA Arizona', 'us.az'),
    offset='-0700',
    offset_delta=DateTimeDelta(0,0,-420)
)


# -------------------------------------------------
# DAYLIGHT SAVING TIMEZONES
#
# The following timezones implement daylight saving time.
# Their political DST rules are in the RelativeDateTime objects
# in dst_forward and dst_back.

# USA/Mexico Central
# 
# The "default" Mexico timezone.

_make_timezone(
    TimezoneDST, 
    ('CT', 'Central', 'US/Central', 'USA Central', 'EEUU Central', 
     'EUA Central', 'us.tx', 'us.il',
     'Mexico', 'M\351xico', 'Mexico Central', 
     'M\351xico Central', 'mx'),
    offset='-0600',
    offset_delta=DateTimeDelta(0,0,-360),
    dst_offset='-0500',
    dst_delta=DateTimeDelta(0,0,-300),
    dst_forward=RelativeDateTime(hour=2, minute=0, month=4, second=0, weekday=(6, 1)),
    dst_back=RelativeDateTime(hour=2, minute=0, month=10, second=0, weekday=(6, -1))
)



# USA Eastern

_make_timezone(
    TimezoneDST, 
    ('ET', 'Eastern', 'US/Eastern', 'USA Eastern', 'EEUU Este', 
     'EUA Leste', 'us.fl', 'us.ny'),
    offset='-0500',
    offset_delta=DateTimeDelta(0,0,-300),
    dst_offset='-0400',
    dst_delta=DateTimeDelta(0,0,-240),
    dst_forward=RelativeDateTime(hour=2, minute=0, month=4, second=0, weekday=(6, 1)),
    dst_back=RelativeDateTime(hour=2, minute=0, month=10, second=0, weekday=(6, -1))
)

# USA Mountain
# 
# Also Baja Sur in Mexico.

_make_timezone(
    TimezoneDST, 
    ('MT', 'Mountain', 'US/Mountain', 'USA Mountain', 
     'EEUU Monta\361a', 'EUA Montanha', 
     'Mexico BajaSur', 'M\351xico BajaSur', 'BajaSur',
     'Mexico Mountain', 'M\351xico Monta\361a'),
    offset='-0700',
    offset_delta=DateTimeDelta(0,0,-420),
    dst_offset='-0600',
    dst_delta=DateTimeDelta(0,0,-360),
    dst_forward=RelativeDateTime(hour=2, minute=0, month=4, second=0, weekday=(6, 1)),
    dst_back=RelativeDateTime(hour=2, minute=0, month=10, second=0, weekday=(6, -1))
)



# USA Pacific
#
# also includes Baja Norte in Mexico.

_make_timezone(
    TimezoneDST, 
    ('PT', 'Pacific', 'US/Pacific', 'USA Pacific', 'EEUU Pac\355fico', 
     'EUA Pacifico', 'us.ca', 
     'Mexico BajaNorte', 'M\351xico BajaNorte', 'BajaNorte',
     'Mexico Pacific', 'M\351xico Pac\355fico',),
    offset='-0800',
    offset_delta=DateTimeDelta(0,0,-480),
    dst_offset='-0700',
    dst_delta=DateTimeDelta(0,0,-420),
    dst_forward=RelativeDateTime(hour=2, minute=0, month=4, second=0, weekday=(6, 1)),
    dst_back=RelativeDateTime(hour=2, minute=0, month=10, second=0, weekday=(6, -1))
)

# Brazil
# 
# Brasil has other timezones, but nobody with a computer lives there. :)
# The country also hasn't fixed its DST rules, but instead "decrees" them
# every year. For 1999/2000, however, they have decreed Oct 3 and Feb 27,
# which happen to be the first Sunday and last Sunday of those months.
# So I'm going to guess that Brazil gets its act together and sticks
# with this rule in the future.

_make_timezone(
    TimezoneDST, 
    ('Brazil', 'Brasil', 'br'),
    offset='-0300',
    offset_delta=DateTimeDelta(0,0,-180),
    dst_offset='-0200',
    dst_delta=DateTimeDelta(0,0,-120),
    dst_forward=RelativeDateTime(hour=0, minute=0, month=10, second=0, weekday=(6, 1)),
    dst_back=RelativeDateTime(hour=0, minute=0, month=2, second=0, weekday=(6, -1))
)

# Chile
_make_timezone(
    TimezoneDST, 
    ('Chile', 'cl'),
    offset='-0400',
    offset_delta=DateTimeDelta(0,0,-240),
    dst_offset='-0300',
    dst_delta=DateTimeDelta(0,0,-180),
    dst_forward=RelativeDateTime(day=15, hour=0, minute=0, month=10, second=0, weekday=(6, 0)),
    dst_back=RelativeDateTime(day=15, hour=0, minute=0, month=3, second=0, weekday=(6, 0))
)


# Cuba
_make_timezone(
    TimezoneDST, 
    ('Cuba', 'cu'),
    offset='-0500',
    offset_delta=DateTimeDelta(0,0,-300),
    dst_offset='-0400',
    dst_delta=DateTimeDelta(0,0,-240),
    dst_forward=RelativeDateTime(day=20, hour=0, minute=0, month=3, second=0, weekday=(6, 0)),
    dst_back=RelativeDateTime(day=14, hour=0, minute=0, month=10, second=0, weekday=(6, 0))
)


# Paraguay
_make_timezone(
    TimezoneDST, 
    ('Paraguay', 'Paraguai', 'py'),
    offset='-0400',
    offset_delta=DateTimeDelta(0,0,-240),
    dst_offset='-0300',
    dst_delta=DateTimeDelta(0,0,-180),
    dst_forward=RelativeDateTime(day=1, hour=0, minute=0, month=10, second=0),
    dst_back=RelativeDateTime(day=1, hour=0, minute=0, month=4, second=0)
)



# Portugal
_make_timezone(
    TimezoneDST, 
    ('Portugal', 'pt'),
    offset='-0000',
    offset_delta=DateTimeDelta(0,0,0),
    dst_offset='+0100',
    dst_delta=DateTimeDelta(0,0,60),
    dst_forward=RelativeDateTime(hour=1, minute=0, month=3, second=0, weekday=(6, -1)),
    dst_back=RelativeDateTime(hour=1, minute=0, month=10, second=0, weekday=(6, -1))
)



# Spain
_make_timezone(
    TimezoneDST, 
    ('Spain', 'Espa\361a', 'Espanha', 'es'),
    offset='+0100',
    offset_delta=DateTimeDelta(0,0,60),
    dst_offset='+0200',
    dst_delta=DateTimeDelta(0,0,120),
    dst_forward=RelativeDateTime(hour=1, minute=0, month=3, second=0, weekday=(6, -1)),
    dst_back=RelativeDateTime(hour=1, minute=0, month=10, second=0, weekday=(6, -1))
)

# silly timezone dumping function

def timezones():
    """
    A reporting function which will
    return a list of descriptions of each
    Timezone objects available in this module.
    Each item in the returned list is a dictionary
    with the following entries:
    
    "names": a list of name strings which can identify the timezone
    "offset": the offset from UTC when daylight saving time is not in effect
    "dst_offset": the offset from UTC when daylight saving is in effect
    
    """

    tl = []
    rd = {}
    # I'm exploiting that timezones have unique id()
    # in the interpreter
    for k, v in _tzdict.items():
  # skip the local timezone
  if isinstance(v, LocalTimezone): continue
  key = hash( id(v) )
  if not rd.has_key(key):
      rd[key] = (v, [k])
  else:
      rd[key][1].append(k)

    for tz, names in rd.values():
  o = tz.__dict__['offset']
  if tz.__dict__.has_key('dst_offset'):
      do = tz.__dict__['dst_offset']
  else: do = None
  tl.append( {'offset': o, 'dst_offset': do, 'names': names} )

    return tl

www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.