##############################################################################
#
# Copyright (c) 2007 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""
Pytz timezone support.
"""

import pytz
import pytz.reference
from pytz.tzinfo import StaticTzInfo, memorized_timedelta
from datetime import datetime, timedelta
from interfaces import DateTimeError

EPOCH = datetime.utcfromtimestamp(0).replace(tzinfo=pytz.utc)

_numeric_timezone_data = {
'GMT':      ('GMT',      0, 1, [], '', [(0, 0, 0)],      'GMT\000'),
'GMT+0':    ('GMT+0',    0, 1, [], '', [(0, 0, 0)],      'GMT+0000\000'),
'GMT+1':    ('GMT+1',    0, 1, [], '', [(3600, 0, 0)],   'GMT+0100\000'),
'GMT+2':    ('GMT+2',    0, 1, [], '', [(7200, 0, 0)],   'GMT+0200\000'),
'GMT+3':    ('GMT+3',    0, 1, [], '', [(10800, 0, 0)],  'GMT+0300\000'),
'GMT+4':    ('GMT+4',    0, 1, [], '', [(14400, 0, 0)],  'GMT+0400\000'),
'GMT+5':    ('GMT+5',    0, 1, [], '', [(18000, 0, 0)],  'GMT+0500\000'),
'GMT+6':    ('GMT+6',    0, 1, [], '', [(21600, 0, 0)],  'GMT+0600\000'),
'GMT+7':    ('GMT+7',    0, 1, [], '', [(25200, 0, 0)],  'GMT+0700\000'),
'GMT+8':    ('GMT+8',    0, 1, [], '', [(28800, 0, 0)],  'GMT+0800\000'),
'GMT+9':    ('GMT+9',    0, 1, [], '', [(32400, 0, 0)],  'GMT+0900\000'),
'GMT+10':   ('GMT+10',   0, 1, [], '', [(36000, 0, 0)],  'GMT+1000\000'),
'GMT+11':   ('GMT+11',   0, 1, [], '', [(39600, 0, 0)],  'GMT+1100\000'),
'GMT+12':   ('GMT+12',   0, 1, [], '', [(43200, 0, 0)],  'GMT+1200\000'),
'GMT+13':   ('GMT+13',   0, 1, [], '', [(46800, 0, 0)],  'GMT+1300\000'),

'GMT-1':    ('GMT-1',    0, 1, [], '', [(-3600, 0, 0)],  'GMT-0100\000'),
'GMT-2':    ('GMT-2',    0, 1, [], '', [(-7200, 0, 0)],  'GMT-0200\000'),
'GMT-3':    ('GMT-3',    0, 1, [], '', [(-10800, 0, 0)], 'GMT-0300\000'),
'GMT-4':    ('GMT-4',    0, 1, [], '', [(-14400, 0, 0)], 'GMT-0400\000'),
'GMT-5':    ('GMT-5',    0, 1, [], '', [(-18000, 0, 0)], 'GMT-0500\000'),
'GMT-6':    ('GMT-6',    0, 1, [], '', [(-21600, 0, 0)], 'GMT-0600\000'),
'GMT-7':    ('GMT-7',    0, 1, [], '', [(-25200, 0, 0)], 'GMT-0700\000'),
'GMT-8':    ('GMT-8',    0, 1, [], '', [(-28800, 0, 0)], 'GMT-0800\000'),
'GMT-9':    ('GMT-9',    0, 1, [], '', [(-32400, 0, 0)], 'GMT-0900\000'),
'GMT-10':   ('GMT-10',   0, 1, [], '', [(-36000, 0, 0)], 'GMT-1000\000'),
'GMT-11':   ('GMT-11',   0, 1, [], '', [(-39600, 0, 0)], 'GMT-1100\000'),
'GMT-12':   ('GMT-12',   0, 1, [], '', [(-43200, 0, 0)], 'GMT-1200\000'),

'GMT+0130': ('GMT+0130', 0, 1, [], '', [(5400,  0, 0)],  'GMT+0130\000'),
'GMT+0230': ('GMT+0230', 0, 1, [], '', [(9000,  0, 0)],  'GMT+0230\000'),
'GMT+0330': ('GMT+0330', 0, 1, [], '', [(12600, 0, 0)],  'GMT+0330\000'),
'GMT+0430': ('GMT+0430', 0, 1, [], '', [(16200, 0, 0)],  'GMT+0430\000'),
'GMT+0530': ('GMT+0530', 0, 1, [], '', [(19800, 0, 0)],  'GMT+0530\000'),
'GMT+0630': ('GMT+0630', 0, 1, [], '', [(23400, 0, 0)],  'GMT+0630\000'),
'GMT+0730': ('GMT+0730', 0, 1, [], '', [(27000, 0, 0)],  'GMT+0730\000'),
'GMT+0830': ('GMT+0830', 0, 1, [], '', [(30600, 0, 0)],  'GMT+0830\000'),
'GMT+0930': ('GMT+0930', 0, 1, [], '', [(34200, 0, 0)],  'GMT+0930\000'),
'GMT+1030': ('GMT+1030', 0, 1, [], '', [(37800, 0, 0)],  'GMT+1030\000'),
'GMT+1130': ('GMT+1130', 0, 1, [], '', [(41400, 0, 0)],  'GMT+1130\000'),
'GMT+1230': ('GMT+1230', 0, 1, [], '', [(45000, 0, 0)],  'GMT+1230\000'),

'GMT-0130': ('GMT-0130', 0, 1, [], '', [(-5400,  0, 0)], 'GMT-0130\000'),
'GMT-0230': ('GMT-0230', 0, 1, [], '', [(-9000,  0, 0)], 'GMT-0230\000'),
'GMT-0330': ('GMT-0330', 0, 1, [], '', [(-12600, 0, 0)], 'GMT-0330\000'),
'GMT-0430': ('GMT-0430', 0, 1, [], '', [(-16200, 0, 0)], 'GMT-0430\000'),
'GMT-0530': ('GMT-0530', 0, 1, [], '', [(-19800, 0, 0)], 'GMT-0530\000'),
'GMT-0630': ('GMT-0630', 0, 1, [], '', [(-23400, 0, 0)], 'GMT-0630\000'),
'GMT-0730': ('GMT-0730', 0, 1, [], '', [(-27000, 0, 0)], 'GMT-0730\000'),
'GMT-0830': ('GMT-0830', 0, 1, [], '', [(-30600, 0, 0)], 'GMT-0830\000'),
'GMT-0930': ('GMT-0930', 0, 1, [], '', [(-34200, 0, 0)], 'GMT-0930\000'),
'GMT-1030': ('GMT-1030', 0, 1, [], '', [(-37800, 0, 0)], 'GMT-1030\000'),
'GMT-1130': ('GMT-1130', 0, 1, [], '', [(-41400, 0, 0)], 'GMT-1130\000'),
'GMT-1230': ('GMT-1230', 0, 1, [], '', [(-45000, 0, 0)], 'GMT-1230\000'),

}

# These are the timezones not in pytz.common_timezones
_old_zlst = [

'AST',  'AT',  'BST',  'BT',  'CCT',
'CET',  'CST',  'Cuba',  'EADT',  'EAST',
'EEST',  'EET',  'EST',  'Egypt',  'FST',
'FWT',  'GB-Eire',  'GMT+0100',  'GMT+0130',  'GMT+0200',
'GMT+0230',  'GMT+0300',  'GMT+0330',  'GMT+0400',  'GMT+0430',
'GMT+0500',  'GMT+0530',  'GMT+0600',  'GMT+0630',  'GMT+0700',
'GMT+0730',  'GMT+0800',  'GMT+0830',  'GMT+0900',  'GMT+0930',
'GMT+1',  'GMT+1000',  'GMT+1030',  'GMT+1100',  'GMT+1130',
'GMT+1200',  'GMT+1230',  'GMT+1300',  'GMT-0100',  'GMT-0130',
'GMT-0200',  'GMT-0300',  'GMT-0400',  'GMT-0500',  'GMT-0600',
'GMT-0630',  'GMT-0700',  'GMT-0730',  'GMT-0800',  'GMT-0830',
'GMT-0900',  'GMT-0930',  'GMT-1000',  'GMT-1030',  'GMT-1100',
'GMT-1130',  'GMT-1200',  'GMT-1230',  'GST',  'Greenwich',
'Hongkong',  'IDLE',  'IDLW',  'Iceland',  'Iran',
'Israel',  'JST',  'Jamaica',  'Japan',  'MEST',
'MET',  'MEWT',  'MST',  'NT',  'NZDT',
'NZST',  'NZT',  'PST',  'Poland',  'SST',
'SWT',  'Singapore',  'Turkey',  'UCT',  'UT',
'Universal',  'WADT',  'WAST',  'WAT',  'WET',
'ZP4',  'ZP5',  'ZP6',

]

_old_zmap={

'aest':'GMT+10', 'aedt':'GMT+11',
'aus eastern standard time':'GMT+10',
'sydney standard time':'GMT+10',
'tasmania standard time':'GMT+10',
'e. australia standard time':'GMT+10',
'aus central standard time':'GMT+0930',
'cen. australia standard time':'GMT+0930',
'w. australia standard time':'GMT+8',

'central europe standard time':'GMT+1',
'eastern standard time':'US/Eastern',
'us eastern standard time':'US/Eastern',
'central standard time':'US/Central',
'mountain standard time':'US/Mountain',
'pacific standard time':'US/Pacific',
'mst':'US/Mountain','pst':'US/Pacific',
'cst':'US/Central','est':'US/Eastern',

'gmt+0000':'GMT+0', 'gmt+0':'GMT+0',

'gmt+0100':'GMT+1', 'gmt+0200':'GMT+2', 'gmt+0300':'GMT+3',
'gmt+0400':'GMT+4', 'gmt+0500':'GMT+5', 'gmt+0600':'GMT+6',
'gmt+0700':'GMT+7', 'gmt+0800':'GMT+8', 'gmt+0900':'GMT+9',
'gmt+1000':'GMT+10','gmt+1100':'GMT+11','gmt+1200':'GMT+12',
'gmt+1300':'GMT+13',
'gmt-0100':'GMT-1', 'gmt-0200':'GMT-2', 'gmt-0300':'GMT-3',
'gmt-0400':'GMT-4', 'gmt-0500':'GMT-5', 'gmt-0600':'GMT-6',
'gmt-0700':'GMT-7', 'gmt-0800':'GMT-8', 'gmt-0900':'GMT-9',
'gmt-1000':'GMT-10','gmt-1100':'GMT-11','gmt-1200':'GMT-12',

'gmt+1': 'GMT+1', 'gmt+2': 'GMT+2', 'gmt+3': 'GMT+3',
'gmt+4': 'GMT+4', 'gmt+5': 'GMT+5', 'gmt+6': 'GMT+6',
'gmt+7': 'GMT+7', 'gmt+8': 'GMT+8', 'gmt+9': 'GMT+9',
'gmt+10':'GMT+10','gmt+11':'GMT+11','gmt+12':'GMT+12',
'gmt+13':'GMT+13',
'gmt-1': 'GMT-1', 'gmt-2': 'GMT-2', 'gmt-3': 'GMT-3',
'gmt-4': 'GMT-4', 'gmt-5': 'GMT-5', 'gmt-6': 'GMT-6',
'gmt-7': 'GMT-7', 'gmt-8': 'GMT-8', 'gmt-9': 'GMT-9',
'gmt-10':'GMT-10','gmt-11':'GMT-11','gmt-12':'GMT-12',

'gmt+130':'GMT+0130',  'gmt+0130':'GMT+0130',
'gmt+230':'GMT+0230',  'gmt+0230':'GMT+0230',
'gmt+330':'GMT+0330',  'gmt+0330':'GMT+0330',
'gmt+430':'GMT+0430',  'gmt+0430':'GMT+0430',
'gmt+530':'GMT+0530',  'gmt+0530':'GMT+0530',
'gmt+630':'GMT+0630',  'gmt+0630':'GMT+0630',
'gmt+730':'GMT+0730',  'gmt+0730':'GMT+0730',
'gmt+830':'GMT+0830',  'gmt+0830':'GMT+0830',
'gmt+930':'GMT+0930',  'gmt+0930':'GMT+0930',
'gmt+1030':'GMT+1030',
'gmt+1130':'GMT+1130',
'gmt+1230':'GMT+1230',

'gmt-130':'GMT-0130',  'gmt-0130':'GMT-0130',
'gmt-230':'GMT-0230',  'gmt-0230':'GMT-0230',
'gmt-330':'GMT-0330',  'gmt-0330':'GMT-0330',
'gmt-430':'GMT-0430',  'gmt-0430':'GMT-0430',
'gmt-530':'GMT-0530',  'gmt-0530':'GMT-0530',
'gmt-630':'GMT-0630',  'gmt-0630':'GMT-0630',
'gmt-730':'GMT-0730',  'gmt-0730':'GMT-0730',
'gmt-830':'GMT-0830',  'gmt-0830':'GMT-0830',
'gmt-930':'GMT-0930',  'gmt-0930':'GMT-0930',
'gmt-1030':'GMT-1030',
'gmt-1130':'GMT-1130',
'gmt-1230':'GMT-1230',

'ut':'Universal',
'bst':'GMT+1', 'mest':'GMT+2', 'sst':'GMT+2',
'fst':'GMT+2', 'wadt':'GMT+8', 'eadt':'GMT+11', 'nzdt':'GMT+13',
'wet':'GMT', 'wat':'GMT-1', 'at':'GMT-2', 'ast':'GMT-4',
'nt':'GMT-11', 'idlw':'GMT-12', 'cet':'GMT+1', 'cest':'GMT+2',
'met':'GMT+1',
'mewt':'GMT+1', 'swt':'GMT+1', 'fwt':'GMT+1', 'eet':'GMT+2',
'eest':'GMT+3',
'bt':'GMT+3', 'zp4':'GMT+4', 'zp5':'GMT+5', 'zp6':'GMT+6',
'wast':'GMT+7', 'cct':'GMT+8', 'jst':'GMT+9', 'east':'GMT+10',
'gst':'GMT+10', 'nzt':'GMT+12', 'nzst':'GMT+12', 'idle':'GMT+12',
'ret':'GMT+4', 'ist': 'GMT+0530', 'edt' : 'GMT-4'

}

def _static_timezone_factory(data):
    zone = data[0]
    cls = type(zone, (StaticTzInfo,), dict(
        zone=zone,
        _utcoffset=memorized_timedelta(data[5][0][0]),
        _tzname=data[6][:-1] )) # strip the trailing null
    return cls()

_numeric_timezones = dict((key, _static_timezone_factory(data))
                           for key, data in _numeric_timezone_data.items())


class Timezone:
    """
    Timezone information returned by PytzCache.__getitem__
    Adapts datetime.tzinfo object to DateTime._timezone interface
    """
    def __init__(self, tzinfo):
        self.tzinfo = tzinfo

    def info(self, t=None):
        if t is None:
            dt = datetime.utcnow().replace(tzinfo=pytz.utc)
        else:
            dt = EPOCH + timedelta(0, t) # can't use utcfromtimestamp past 2038

        # need to normalize tzinfo for the datetime to deal with
        # daylight savings time.
        normalized_dt = self.tzinfo.normalize(dt.astimezone(self.tzinfo))
        normalized_tzinfo = normalized_dt.tzinfo

        offset = normalized_tzinfo.utcoffset(normalized_dt)
        secs = offset.days * 24 * 60 * 60 + offset.seconds
        dst = normalized_tzinfo.dst(normalized_dt)
        if dst == timedelta(0):
            is_dst = 0
        else:
            is_dst = 1
        return secs, is_dst, normalized_tzinfo.tzname(normalized_dt)


class PytzCache:
    """
    Reimplementation of the DateTime._cache class that uses for timezone info
    """

    _zlst = pytz.common_timezones + _old_zlst # used by DateTime.TimeZones
    _zmap = dict((name.lower(), name) for name in pytz.all_timezones)
    _zmap.update(_old_zmap) # These must take priority
    _zidx = _zmap.keys()

    def __getitem__(self, key):
        name = self._zmap.get(key.lower(), key) # fallback to key
        try:
            return Timezone(pytz.timezone(name))
        except pytz.UnknownTimeZoneError:
            try:
                return Timezone(_numeric_timezones[name])
            except KeyError:
                raise DateTimeError,'Unrecognized timezone: %s' % key
