diff options
-rw-r--r-- | .gitignore | 37 | ||||
-rw-r--r-- | .travis.yml | 13 | ||||
-rw-r--r-- | Makefile | 1288 | ||||
-rw-r--r-- | README.txt | 14 | ||||
-rw-r--r-- | conf.py | 182 | ||||
-rw-r--r-- | deps.txt | 18 | ||||
-rw-r--r-- | gen_pot.py | 38 | ||||
-rw-r--r-- | gen_tests.py | 49 | ||||
-rw-r--r-- | gen_tzinfo.py | 156 | ||||
-rw-r--r-- | src/LICENSE.txt | 19 | ||||
-rw-r--r-- | src/MANIFEST.in | 5 | ||||
-rw-r--r-- | src/README.txt | 582 | ||||
-rw-r--r-- | src/pytz/__init__.py | 503 | ||||
-rw-r--r-- | src/pytz/exceptions.py | 48 | ||||
-rw-r--r-- | src/pytz/lazy.py | 172 | ||||
-rw-r--r-- | src/pytz/locales/pytz.pot | 2 | ||||
-rw-r--r-- | src/pytz/reference.py | 140 | ||||
-rw-r--r-- | src/pytz/tests/test_docs.py | 34 | ||||
-rw-r--r-- | src/pytz/tests/test_lazy.py | 315 | ||||
-rw-r--r-- | src/pytz/tests/test_tzinfo.py | 863 | ||||
-rw-r--r-- | src/pytz/tzfile.py | 134 | ||||
-rw-r--r-- | src/pytz/tzinfo.py | 577 | ||||
l--------- | src/pytz/zoneinfo | 1 | ||||
-rw-r--r-- | src/setup.cfg | 2 | ||||
-rw-r--r-- | src/setup.py | 66 | ||||
-rw-r--r-- | test_zdump.py | 138 | ||||
-rw-r--r-- | tz/.gitignore | 26 | ||||
-rw-r--r-- | tz/CONTRIBUTING (renamed from CONTRIBUTING) | 0 | ||||
-rw-r--r-- | tz/LICENSE (renamed from LICENSE) | 0 | ||||
-rw-r--r-- | tz/Makefile | 1123 | ||||
-rw-r--r-- | tz/NEWS (renamed from NEWS) | 0 | ||||
-rw-r--r-- | tz/README (renamed from README) | 0 | ||||
-rw-r--r-- | tz/africa (renamed from africa) | 0 | ||||
-rw-r--r-- | tz/antarctica (renamed from antarctica) | 0 | ||||
-rw-r--r-- | tz/asctime.c (renamed from asctime.c) | 0 | ||||
-rw-r--r-- | tz/asia (renamed from asia) | 0 | ||||
-rw-r--r-- | tz/australasia (renamed from australasia) | 0 | ||||
-rw-r--r-- | tz/backward (renamed from backward) | 0 | ||||
-rw-r--r-- | tz/backzone (renamed from backzone) | 0 | ||||
-rw-r--r-- | tz/calendars (renamed from calendars) | 0 | ||||
-rw-r--r-- | tz/checklinks.awk (renamed from checklinks.awk) | 0 | ||||
-rw-r--r-- | tz/checktab.awk (renamed from checktab.awk) | 0 | ||||
-rw-r--r-- | tz/date.1 (renamed from date.1) | 0 | ||||
-rw-r--r-- | tz/date.c (renamed from date.c) | 0 | ||||
-rw-r--r-- | tz/difftime.c (renamed from difftime.c) | 0 | ||||
-rw-r--r-- | tz/etcetera (renamed from etcetera) | 0 | ||||
-rw-r--r-- | tz/europe (renamed from europe) | 0 | ||||
-rw-r--r-- | tz/factory (renamed from factory) | 0 | ||||
-rw-r--r-- | tz/iso3166.tab (renamed from iso3166.tab) | 0 | ||||
-rw-r--r-- | tz/leap-seconds.list (renamed from leap-seconds.list) | 0 | ||||
-rw-r--r-- | tz/leapseconds.awk (renamed from leapseconds.awk) | 0 | ||||
-rw-r--r-- | tz/localtime.c (renamed from localtime.c) | 0 | ||||
-rw-r--r-- | tz/newctime.3 (renamed from newctime.3) | 0 | ||||
-rw-r--r-- | tz/newstrftime.3 (renamed from newstrftime.3) | 0 | ||||
-rw-r--r-- | tz/newtzset.3 (renamed from newtzset.3) | 0 | ||||
-rw-r--r-- | tz/northamerica (renamed from northamerica) | 0 | ||||
-rw-r--r-- | tz/pacificnew (renamed from pacificnew) | 0 | ||||
-rw-r--r-- | tz/private.h (renamed from private.h) | 0 | ||||
-rw-r--r-- | tz/southamerica (renamed from southamerica) | 0 | ||||
-rw-r--r-- | tz/strftime.c (renamed from strftime.c) | 0 | ||||
-rw-r--r-- | tz/systemv (renamed from systemv) | 0 | ||||
-rw-r--r-- | tz/theory.html (renamed from theory.html) | 0 | ||||
-rw-r--r-- | tz/time2posix.3 (renamed from time2posix.3) | 0 | ||||
-rw-r--r-- | tz/tz-art.html (renamed from tz-art.html) | 0 | ||||
-rw-r--r-- | tz/tz-how-to.html (renamed from tz-how-to.html) | 0 | ||||
-rw-r--r-- | tz/tz-link.html (renamed from tz-link.html) | 0 | ||||
-rw-r--r-- | tz/tzfile.5 (renamed from tzfile.5) | 0 | ||||
-rw-r--r-- | tz/tzfile.h (renamed from tzfile.h) | 0 | ||||
-rw-r--r-- | tz/tzselect.8 (renamed from tzselect.8) | 0 | ||||
-rw-r--r-- | tz/tzselect.ksh (renamed from tzselect.ksh) | 0 | ||||
-rw-r--r-- | tz/workman.sh (renamed from workman.sh) | 0 | ||||
-rw-r--r-- | tz/yearistype.sh (renamed from yearistype.sh) | 0 | ||||
-rw-r--r-- | tz/zdump.8 (renamed from zdump.8) | 0 | ||||
-rw-r--r-- | tz/zdump.c (renamed from zdump.c) | 0 | ||||
-rw-r--r-- | tz/zic.8 (renamed from zic.8) | 0 | ||||
-rw-r--r-- | tz/zic.c (renamed from zic.c) | 0 | ||||
-rw-r--r-- | tz/ziguard.awk (renamed from ziguard.awk) | 0 | ||||
-rw-r--r-- | tz/zishrink.awk (renamed from zishrink.awk) | 0 | ||||
-rw-r--r-- | tz/zone.tab (renamed from zone.tab) | 0 | ||||
-rw-r--r-- | tz/zone1970.tab (renamed from zone1970.tab) | 0 | ||||
-rwxr-xr-x | tz/zoneinfo2tdf.pl (renamed from zoneinfo2tdf.pl) | 0 |
81 files changed, 5404 insertions, 1141 deletions
@@ -1,26 +1,13 @@ -# Files intentionally not tracked by Git. -# This file is in the public domain. -*.a -*.asc -*.diff -*.i -*.o -*.orig -*.patch -*.rej -*.tar -*.tar.* -*.txt -*.tzs -*.zi *~ -ChangeLog -check_* -date -leapseconds -tzselect -version -version.h -yearistype -zdump -zic +*.py[co] + +# Build-related files +.stamp-dist +.stamp-tzinfo +.stamp-zoneinfo +# For whatever reason, build/ is versioned, so the * is necessary. +build/* +tz/version + +# Generated datafile for test_zdump.py +zdump.out diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7ccf00f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +# vim: set filetype=yaml sw=2: +sudo: required +dist: trusty +before_install: + - sudo locale-gen + - sudo add-apt-repository -y ppa:deadsnakes/ppa + - sudo apt-get update -y -qq + - sudo apt-get install -y python-all python-all-dev python3-all python3-all-dev build-essential python-setuptools python3-setuptools python-wheel python3-wheel python3-docutils python3-sphinx python3-flake8 python-flake8 python2.4-complete python2.5-complete python2.6-complete python3.1-complete python3.2-complete python3.3-complete python3.5-complete python2.4-gdbm python2.4-gdbm-dbg python3.6 wget python-pip python3-pip python3.7 python3.7-dev python3.7-distutils + - sudo pip install wheel + - sudo pip3 install wheel + +install: true +script: make test TESTARGS= @@ -1,1123 +1,179 @@ -# Make and install tzdb code and data. - -# This file is in the public domain, so clarified as of -# 2009-05-17 by Arthur David Olson. - -# Package name for the code distribution. -PACKAGE= tzcode - -# Version number for the distribution, overridden in the 'tarballs' rule below. -VERSION= unknown - -# Email address for bug reports. -BUGEMAIL= tz@iana.org - -# DATAFORM selects the data format. -# Available formats represent essentially the same data, albeit -# possibly with minor discrepancies that users are not likely to notice. -# To get new features and the best data right away, use: -# DATAFORM= vanguard -# To wait a while before using new features, to give downstream users -# time to upgrade zic (the default), use: -# DATAFORM= main -# To wait even longer for new features, use: -# DATAFORM= rearguard -DATAFORM= main - -# Change the line below for your timezone (after finding the one you want in -# one of the $(TDATA) source files, or adding it to a source file). -# Alternatively, if you discover you've got the wrong timezone, you can just -# zic -l rightzone -# to correct things. -# Use the command -# make zonenames -# to get a list of the values you can use for LOCALTIME. - -LOCALTIME= GMT - -# The POSIXRULES macro controls interpretation of nonstandard and obsolete -# POSIX-like TZ settings like TZ='EET-2EEST' that lack DST transition rules. -# In the reference implementation, if you want something other than Eastern -# United States time as a template for handling these settings, you can -# change the line below (after finding the timezone you want in the -# one of the $(TDATA) source files, or adding it to a source file). -# A setting like TZ='EET-2EEST' is supposed to use the rules in the -# template file to determine "spring forward" and "fall back" days and -# times; the environment variable itself specifies UT offsets of standard and -# daylight saving time. -# Alternatively, if you discover you've got the wrong timezone, you can just -# zic -p rightzone -# to correct things. -# Use the command -# make zonenames -# to get a list of the values you can use for POSIXRULES. -# -# If POSIXRULES is empty, no template is installed; this is the intended -# future default for POSIXRULES. +# Build the pytz libraries # -# Nonempty POSIXRULES is obsolete and should not be relied on, because: -# * It does not work correctly in popular implementations such as GNU/Linux. -# * It does not work in the tzdb implementation for timestamps after 2037. -# * It is incompatible with 'zic -b slim' if POSIXRULES specifies transitions -# at standard time or UT rather than at local time. -# In short, software should avoid ruleless settings like TZ='EET-2EEST' -# and so should not depend on the value of POSIXRULES. - -POSIXRULES= America/New_York - -# Also see TZDEFRULESTRING below, which takes effect only -# if the time zone files cannot be accessed. - - -# Installation locations. -# -# The defaults are suitable for Debian, except that if REDO is -# posix_right or right_posix then files that Debian puts under -# /usr/share/zoneinfo/posix and /usr/share/zoneinfo/right are instead -# put under /usr/share/zoneinfo-posix and /usr/share/zoneinfo-leaps, -# respectively. Problems with the Debian approach are discussed in -# the commentary for the right_posix rule (below). - -# Destination directory, which can be used for staging. -# 'make DESTDIR=/stage install' installs under /stage (e.g., to -# /stage/etc/localtime instead of to /etc/localtime). Files under -# /stage are not intended to work as-is, but can be copied by hand to -# the root directory later. If DESTDIR is empty, 'make install' does -# not stage, but installs directly into production locations. -DESTDIR = - -# Everything is installed into subdirectories of TOPDIR, and used there. -# TOPDIR should be empty (meaning the root directory), -# or a directory name that does not end in "/". -# TOPDIR should be empty or an absolute name unless you're just testing. -TOPDIR = - -# The default local timezone is taken from the file TZDEFAULT. -TZDEFAULT = $(TOPDIR)/etc/localtime - -# The subdirectory containing installed program and data files, and -# likewise for installed files that can be shared among architectures. -# These should be relative file names. -USRDIR = usr -USRSHAREDIR = $(USRDIR)/share - -# "Compiled" timezone information is placed in the "TZDIR" directory -# (and subdirectories). -# TZDIR_BASENAME should not contain "/" and should not be ".", ".." or empty. -TZDIR_BASENAME= zoneinfo -TZDIR = $(TOPDIR)/$(USRSHAREDIR)/$(TZDIR_BASENAME) - -# The "tzselect" and (if you do "make INSTALL") "date" commands go in: -BINDIR = $(TOPDIR)/$(USRDIR)/bin - -# The "zdump" command goes in: -ZDUMPDIR = $(BINDIR) - -# The "zic" command goes in: -ZICDIR = $(TOPDIR)/$(USRDIR)/sbin - -# Manual pages go in subdirectories of. . . -MANDIR = $(TOPDIR)/$(USRSHAREDIR)/man - -# Library functions are put in an archive in LIBDIR. -LIBDIR = $(TOPDIR)/$(USRDIR)/lib - - -# Types to try, as an alternative to time_t. -TIME_T_ALTERNATIVES = $(TIME_T_ALTERNATIVES_HEAD) $(TIME_T_ALTERNATIVES_TAIL) -TIME_T_ALTERNATIVES_HEAD = int64_t -TIME_T_ALTERNATIVES_TAIL = int32_t uint32_t uint64_t - -# What kind of TZif data files to generate. (TZif is the binary time -# zone data format that zic generates; see Internet RFC 8536.) -# If you want only POSIX time, with time values interpreted as -# seconds since the epoch (not counting leap seconds), use -# REDO= posix_only -# below. If you want only "right" time, with values interpreted -# as seconds since the epoch (counting leap seconds), use -# REDO= right_only -# below. If you want both sets of data available, with leap seconds not -# counted normally, use -# REDO= posix_right -# below. If you want both sets of data available, with leap seconds counted -# normally, use -# REDO= right_posix -# below. POSIX mandates that leap seconds not be counted; for compatibility -# with it, use "posix_only" or "posix_right". Use POSIX time on systems with -# leap smearing; this can work better than unsmeared "right" time with -# applications that are not leap second aware, and is closer to unsmeared -# "right" time than unsmeared POSIX time is (e.g., 0.5 vs 1.0 s max error). - -REDO= posix_right - -# To install data in text form that has all the information of the TZif data, -# (optionally incorporating leap second information), use -# TZDATA_TEXT= tzdata.zi leapseconds -# To install text data without leap second information (e.g., because -# REDO='posix_only'), use -# TZDATA_TEXT= tzdata.zi -# To avoid installing text data, use -# TZDATA_TEXT= - -TZDATA_TEXT= leapseconds tzdata.zi - -# For backward-compatibility links for old zone names, use -# BACKWARD= backward -# If you also want the link US/Pacific-New, even though it is confusing -# and is planned to be removed from the database eventually, use -# BACKWARD= backward pacificnew -# To omit these links, use -# BACKWARD= - -BACKWARD= backward - -# If you want out-of-scope and often-wrong data from the file 'backzone', use -# PACKRATDATA= backzone -# To omit this data, use -# PACKRATDATA= - -PACKRATDATA= - -# The name of a locale using the UTF-8 encoding, used during self-tests. -# The tests are skipped if the name does not appear to work on this system. - -UTF8_LOCALE= en_US.utf8 - -# Since "." may not be in PATH... - -YEARISTYPE= ./yearistype - -# Non-default libraries needed to link. -LDLIBS= - -# Add the following to the end of the "CFLAGS=" line as needed to override -# defaults specified in the source code. "-DFOO" is equivalent to "-DFOO=1". -# -DDEPRECATE_TWO_DIGIT_YEARS for optional runtime warnings about strftime -# formats that generate only the last two digits of year numbers -# -DEPOCH_LOCAL if the 'time' function returns local time not UT -# -DEPOCH_OFFSET=N if the 'time' function returns a value N greater -# than what POSIX specifies, assuming local time is UT. -# For example, N is 252460800 on AmigaOS. -# -DHAVE_DECL_ASCTIME_R=0 if <time.h> does not declare asctime_r -# -DHAVE_DECL_ENVIRON if <unistd.h> declares 'environ' -# -DHAVE_DIRECT_H if mkdir needs <direct.h> (MS-Windows) -# -DHAVE_GENERIC=0 if _Generic does not work -# -DHAVE_GETTEXT if 'gettext' works (e.g., GNU/Linux, FreeBSD, Solaris) -# -DHAVE_INCOMPATIBLE_CTIME_R if your system's time.h declares -# ctime_r and asctime_r incompatibly with the POSIX standard -# (Solaris when _POSIX_PTHREAD_SEMANTICS is not defined). -# -DHAVE_INTTYPES_H if you have a non-C99 compiler with <inttypes.h> -# -DHAVE_LINK=0 if your system lacks a link function -# -DHAVE_LOCALTIME_R=0 if your system lacks a localtime_r function -# -DHAVE_LOCALTIME_RZ=0 if you do not want zdump to use localtime_rz -# localtime_rz can make zdump significantly faster, but is nonstandard. -# -DHAVE_POSIX_DECLS=0 if your system's include files do not declare -# functions like 'link' or variables like 'tzname' required by POSIX -# -DHAVE_SNPRINTF=0 if your system lacks the snprintf function -# -DHAVE_STDBOOL_H if you have a non-C99 compiler with <stdbool.h> -# -DHAVE_STDINT_H if you have a non-C99 compiler with <stdint.h> -# -DHAVE_STRFTIME_L if <time.h> declares locale_t and strftime_l -# -DHAVE_STRDUP=0 if your system lacks the strdup function -# -DHAVE_STRTOLL=0 if your system lacks the strtoll function -# -DHAVE_SYMLINK=0 if your system lacks the symlink function -# -DHAVE_SYS_STAT_H=0 if your compiler lacks a <sys/stat.h> -# -DHAVE_SYS_WAIT_H=0 if your compiler lacks a <sys/wait.h> -# -DHAVE_TZSET=0 if your system lacks a tzset function -# -DHAVE_UNISTD_H=0 if your compiler lacks a <unistd.h> -# -Dlocale_t=XXX if your system uses XXX instead of locale_t -# -DRESERVE_STD_EXT_IDS if your platform reserves standard identifiers -# with external linkage, e.g., applications cannot define 'localtime'. -# -Dssize_t=long on hosts like MS-Windows that lack ssize_t -# -DSUPPRESS_TZDIR to not prepend TZDIR to file names; this has -# security implications and is not recommended for general use -# -DTHREAD_SAFE to make localtime.c thread-safe, as POSIX requires; -# not needed by the main-program tz code, which is single-threaded. -# Append other compiler flags as needed, e.g., -pthread on GNU/Linux. -# -Dtime_tz=\"T\" to use T as the time_t type, rather than the system time_t -# This is intended for internal use only; it mangles external names. -# -DTZ_DOMAIN=\"foo\" to use "foo" for gettext domain name; default is "tz" -# -DTZ_DOMAINDIR=\"/path\" to use "/path" for gettext directory; -# the default is system-supplied, typically "/usr/lib/locale" -# -DTZDEFRULESTRING=\",date/time,date/time\" to default to the specified -# DST transitions if the time zone files cannot be accessed -# -DUNINIT_TRAP if reading uninitialized storage can cause problems -# other than simply getting garbage data -# -DUSE_LTZ=0 to build zdump with the system time zone library -# Also set TZDOBJS=zdump.o and CHECK_TIME_T_ALTERNATIVES= below. -# -DZIC_BLOAT_DEFAULT=\"slim\" to default zic's -b option to "slim", and -# similarly for "fat". Fat TZif files work around incompatibilities -# and bugs in some TZif readers, notably readers that mishandle 64-bit -# data in TZif files. Slim TZif files are more efficient and do not -# work around these incompatibilities and bugs. If not given, the -# current default is "fat" but this is intended to change as readers -# requiring fat files often mishandle timestamps after 2037 anyway. -# -DZIC_MAX_ABBR_LEN_WO_WARN=3 -# (or some other number) to set the maximum time zone abbreviation length -# that zic will accept without a warning (the default is 6) -# $(GCC_DEBUG_FLAGS) if you are using recent GCC and want lots of checking -# Select instrumentation via "make GCC_INSTRUMENT='whatever'". -GCC_INSTRUMENT = \ - -fsanitize=undefined -fsanitize-address-use-after-scope \ - -fsanitize-undefined-trap-on-error -fstack-protector -GCC_DEBUG_FLAGS = -DGCC_LINT -g3 -O3 -fno-common \ - $(GCC_INSTRUMENT) \ - -Wall -Wextra \ - -Walloc-size-larger-than=100000 -Warray-bounds=2 \ - -Wbad-function-cast -Wcast-align=strict -Wdate-time \ - -Wdeclaration-after-statement -Wdouble-promotion \ - -Wformat=2 -Wformat-overflow=2 -Wformat-signedness -Wformat-truncation \ - -Winit-self -Wjump-misses-init -Wlogical-op \ - -Wmissing-declarations -Wmissing-prototypes -Wnested-externs \ - -Wold-style-definition -Woverlength-strings -Wpointer-arith \ - -Wshadow -Wshift-overflow=2 -Wstrict-prototypes -Wstringop-overflow=4 \ - -Wstringop-truncation -Wsuggest-attribute=cold \ - -Wsuggest-attribute=const -Wsuggest-attribute=format \ - -Wsuggest-attribute=malloc \ - -Wsuggest-attribute=noreturn -Wsuggest-attribute=pure \ - -Wtrampolines -Wundef -Wuninitialized -Wunused \ - -Wvariadic-macros -Wvla -Wwrite-strings \ - -Wno-address -Wno-format-nonliteral -Wno-sign-compare \ - -Wno-type-limits -Wno-unused-parameter -# -# If your system has a "GMT offset" field in its "struct tm"s -# (or if you decide to add such a field in your system's "time.h" file), -# add the name to a define such as -# -DTM_GMTOFF=tm_gmtoff -# to the end of the "CFLAGS=" line. If not defined, the code attempts to -# guess TM_GMTOFF from other macros; define NO_TM_GMTOFF to suppress this. -# Similarly, if your system has a "zone abbreviation" field, define -# -DTM_ZONE=tm_zone -# and define NO_TM_ZONE to suppress any guessing. These two fields are not -# required by POSIX, but are widely available on GNU/Linux and BSD systems. -# -# The next batch of options control support for external variables -# exported by tzcode. In practice these variables are less useful -# than TM_GMTOFF and TM_ZONE. However, most of them are standardized. -# # -# # To omit or support the external variable "tzname", add one of: -# # -DHAVE_TZNAME=0 -# # -DHAVE_TZNAME=1 -# # to the "CFLAGS=" line. "tzname" is required by POSIX 1988 and later. -# # If not defined, the code attempts to guess HAVE_TZNAME from other macros. -# # Warning: unless time_tz is also defined, HAVE_TZNAME=1 can cause -# # crashes when combined with some platforms' standard libraries, -# # presumably due to memory allocation issues. -# # -# # To omit or support the external variables "timezone" and "daylight", add -# # -DUSG_COMPAT=0 -# # -DUSG_COMPAT=1 -# # to the "CFLAGS=" line; "timezone" and "daylight" are inspired by -# # Unix Systems Group code and are required by POSIX 2008 (with XSI) and later. -# # If not defined, the code attempts to guess USG_COMPAT from other macros. -# # -# # To support the external variable "altzone", add -# # -DALTZONE -# # to the end of the "CFLAGS=" line; although "altzone" appeared in -# # System V Release 3.1 it has not been standardized. -# -# If you want functions that were inspired by early versions of X3J11's work, -# add -# -DSTD_INSPIRED -# to the end of the "CFLAGS=" line. This arranges for the functions -# "tzsetwall", "offtime", "timelocal", "timegm", "timeoff", -# "posix2time", and "time2posix" to be added to the time conversion library. -# "tzsetwall" is like "tzset" except that it arranges for local wall clock -# time (rather than the timezone specified in the TZ environment variable) -# to be used. -# "offtime" is like "gmtime" except that it accepts a second (long) argument -# that gives an offset to add to the time_t when converting it. -# "timelocal" is equivalent to "mktime". -# "timegm" is like "timelocal" except that it turns a struct tm into -# a time_t using UT (rather than local time as "timelocal" does). -# "timeoff" is like "timegm" except that it accepts a second (long) argument -# that gives an offset to use when converting to a time_t. -# "posix2time" and "time2posix" are described in an included manual page. -# X3J11's work does not describe any of these functions. -# Sun has provided "tzsetwall", "timelocal", and "timegm" in SunOS 4.0. -# These functions may well disappear in future releases of the time -# conversion package. -# -# If you don't want functions that were inspired by NetBSD, add -# -DNETBSD_INSPIRED=0 -# to the end of the "CFLAGS=" line. Otherwise, the functions -# "localtime_rz", "mktime_z", "tzalloc", and "tzfree" are added to the -# time library, and if STD_INSPIRED is also defined the functions -# "posix2time_z" and "time2posix_z" are added as well. -# The functions ending in "_z" (or "_rz") are like their unsuffixed -# (or suffixed-by-"_r") counterparts, except with an extra first -# argument of opaque type timezone_t that specifies the timezone. -# "tzalloc" allocates a timezone_t value, and "tzfree" frees it. -# -# If you want to allocate state structures in localtime, add -# -DALL_STATE -# to the end of the "CFLAGS=" line. Storage is obtained by calling malloc. -# -# NIST-PCTS:151-2, Version 1.4, (1993-12-03) is a test suite put -# out by the National Institute of Standards and Technology -# which claims to test C and Posix conformance. If you want to pass PCTS, add -# -DPCTS -# to the end of the "CFLAGS=" line. -# -# If you want strict compliance with XPG4 as of 1994-04-09, add -# -DXPG4_1994_04_09 -# to the end of the "CFLAGS=" line. This causes "strftime" to always return -# 53 as a week number (rather than 52 or 53) for January days before -# January's first Monday when a "%V" format is used and January 1 -# falls on a Friday, Saturday, or Sunday. - -CFLAGS= - -# Linker flags. Default to $(LFLAGS) for backwards compatibility -# to release 2012h and earlier. - -LDFLAGS= $(LFLAGS) - -# For leap seconds, this Makefile uses LEAPSECONDS='-L leapseconds' in -# submake command lines. The default is no leap seconds. - -LEAPSECONDS= - -# The zic command and its arguments. - -zic= ./zic -ZIC= $(zic) $(ZFLAGS) - -# To shrink the size of installed TZif files, -# append "-r @N" to omit data before N-seconds-after-the-Epoch. -# You can also append "-b slim" if that is not already the default; -# see ZIC_BLOAT_DEFAULT above. -# See the zic man page for more about -b and -r. -ZFLAGS= - -# How to use zic to install TZif files. - -ZIC_INSTALL= $(ZIC) -d '$(DESTDIR)$(TZDIR)' $(LEAPSECONDS) - -# The name of a Posix-compliant 'awk' on your system. -# Older 'mawk' versions, such as the 'mawk' in Ubuntu 16.04, might dump core; -# on Ubuntu you can work around this with -# AWK= gawk -AWK= awk - -# The full path name of a Posix-compliant shell, preferably one that supports -# the Korn shell's 'select' statement as an extension. -# These days, Bash is the most popular. -# It should be OK to set this to /bin/sh, on platforms where /bin/sh -# lacks 'select' or doesn't completely conform to Posix, but /bin/bash -# is typically nicer if it works. -KSHELL= /bin/bash - -# Name of curl <https://curl.haxx.se/>, used for HTML validation. -CURL= curl - -# Name of GNU Privacy Guard <https://gnupg.org/>, used to sign distributions. -GPG= gpg - -# The path where SGML DTDs are kept and the catalog file(s) to use when -# validating HTML 4.01. The default should work on both Debian and Red Hat. -SGML_TOPDIR= /usr -SGML_DTDDIR= $(SGML_TOPDIR)/share/xml/w3c-sgml-lib/schema/dtd -SGML_SEARCH_PATH= $(SGML_DTDDIR)/REC-html401-19991224 -SGML_CATALOG_FILES= \ - $(SGML_TOPDIR)/share/doc/w3-recs/html/www.w3.org/TR/1999/REC-html401-19991224/HTML4.cat:$(SGML_TOPDIR)/share/sgml/html/4.01/HTML4.cat - -# The name, arguments and environment of a program to validate HTML 4.01. -# See <http://openjade.sourceforge.net/doc/> for a validator, and -# <https://validator.w3.org/source/> for a validation library. -# Set VALIDATE=':' if you do not have such a program. -VALIDATE = nsgmls -VALIDATE_FLAGS = -s -B -wall -wno-unused-param -VALIDATE_ENV = \ - SGML_CATALOG_FILES='$(SGML_CATALOG_FILES)' \ - SGML_SEARCH_PATH='$(SGML_SEARCH_PATH)' \ - SP_CHARSET_FIXED=YES \ - SP_ENCODING=UTF-8 - -# This expensive test requires USE_LTZ. -# To suppress it, define this macro to be empty. -CHECK_TIME_T_ALTERNATIVES = check_time_t_alternatives - -# SAFE_CHAR is a regular expression that matches a safe character. -# Some parts of this distribution are limited to safe characters; -# others can use any UTF-8 character. -# For now, the safe characters are a safe subset of ASCII. -# The caller must set the shell variable 'sharp' to the character '#', -# since Makefile macros cannot contain '#'. -# TAB_CHAR is a single tab character, in single quotes. -TAB_CHAR= ' ' -SAFE_CHARSET1= $(TAB_CHAR)' !\"'$$sharp'$$%&'\''()*+,./0123456789:;<=>?@' -SAFE_CHARSET2= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\^_`' -SAFE_CHARSET3= 'abcdefghijklmnopqrstuvwxyz{|}~' -SAFE_CHARSET= $(SAFE_CHARSET1)$(SAFE_CHARSET2)$(SAFE_CHARSET3) -SAFE_CHAR= '[]'$(SAFE_CHARSET)'-]' - -# These characters are Latin-1, and so are likely to be displayable -# even in editors with limited character sets. -UNUSUAL_OK_LATIN_1 = «°±»½¾× -# This IPA symbol is represented in Unicode as the composition of -# U+0075 and U+032F, and U+032F is not considered alphabetic by some -# grep implementations that do not grok composition. -UNUSUAL_OK_IPA = u̯ -# Non-ASCII non-letters that OK_CHAR allows, as these characters are -# useful in commentary. -UNUSUAL_OK_CHARSET= $(UNUSUAL_OK_LATIN_1)$(UNUSUAL_OK_IPA) - -# OK_CHAR matches any character allowed in the distributed files. -# This is the same as SAFE_CHAR, except that UNUSUAL_OK_CHARSET and -# multibyte letters are also allowed so that commentary can contain a -# few safe symbols and people's names and can quote non-English sources. -# Other non-letters are limited to ASCII renderings for the -# convenience of maintainers using XEmacs 21.5.34, which by default -# mishandles Unicode characters U+0100 and greater. -OK_CHAR= '[][:alpha:]$(UNUSUAL_OK_CHARSET)'$(SAFE_CHARSET)'-]' - -# SAFE_LINE matches a line of safe characters. -# SAFE_SHARP_LINE is similar, except any OK character can follow '#'; -# this is so that comments can contain non-ASCII characters. -# OK_LINE matches a line of OK characters. -SAFE_LINE= '^'$(SAFE_CHAR)'*$$' -SAFE_SHARP_LINE='^'$(SAFE_CHAR)'*('$$sharp$(OK_CHAR)'*)?$$' -OK_LINE= '^'$(OK_CHAR)'*$$' - -# Flags to give 'tar' when making a distribution. -# Try to use flags appropriate for GNU tar. -GNUTARFLAGS= --numeric-owner --owner=0 --group=0 --mode=go+u,go-w --sort=name -TARFLAGS= `if tar $(GNUTARFLAGS) --version >/dev/null 2>&1; \ - then echo $(GNUTARFLAGS); \ - else :; \ - fi` -# Flags to give 'gzip' when making a distribution. -GZIPFLAGS= -9n - -############################################################################### - -#MAKE= make - -cc= cc -CC= $(cc) -DTZDIR='"$(TZDIR)"' - -AR= ar - -# ':' on typical hosts; 'ranlib' on the ancient hosts that still need ranlib. -RANLIB= : - -TZCOBJS= zic.o -TZDOBJS= zdump.o localtime.o asctime.o strftime.o -DATEOBJS= date.o localtime.o strftime.o asctime.o -LIBSRCS= localtime.c asctime.c difftime.c -LIBOBJS= localtime.o asctime.o difftime.o -HEADERS= tzfile.h private.h -NONLIBSRCS= zic.c zdump.c -NEWUCBSRCS= date.c strftime.c -SOURCES= $(HEADERS) $(LIBSRCS) $(NONLIBSRCS) $(NEWUCBSRCS) \ - tzselect.ksh workman.sh -MANS= newctime.3 newstrftime.3 newtzset.3 time2posix.3 \ - tzfile.5 tzselect.8 zic.8 zdump.8 -MANTXTS= newctime.3.txt newstrftime.3.txt newtzset.3.txt \ - time2posix.3.txt \ - tzfile.5.txt tzselect.8.txt zic.8.txt zdump.8.txt \ - date.1.txt -COMMON= calendars CONTRIBUTING LICENSE Makefile \ - NEWS README theory.html version -WEB_PAGES= tz-art.html tz-how-to.html tz-link.html -CHECK_WEB_PAGES=check_theory.html check_tz-art.html \ - check_tz-how-to.html check_tz-link.html -DOCS= $(MANS) date.1 $(MANTXTS) $(WEB_PAGES) -PRIMARY_YDATA= africa antarctica asia australasia \ - europe northamerica southamerica -YDATA= $(PRIMARY_YDATA) etcetera -NDATA= systemv factory -TDATA_TO_CHECK= $(YDATA) $(NDATA) backward pacificnew -TDATA= $(YDATA) $(NDATA) $(BACKWARD) -ZONETABLES= zone1970.tab zone.tab -TABDATA= iso3166.tab $(TZDATA_TEXT) $(ZONETABLES) -LEAP_DEPS= leapseconds.awk leap-seconds.list -TZDATA_ZI_DEPS= ziguard.awk zishrink.awk version $(TDATA) $(PACKRATDATA) -DSTDATA_ZI_DEPS= ziguard.awk $(TDATA) $(PACKRATDATA) -DATA= $(TDATA_TO_CHECK) backzone iso3166.tab leap-seconds.list \ - leapseconds yearistype.sh $(ZONETABLES) -AWK_SCRIPTS= checklinks.awk checktab.awk leapseconds.awk \ - ziguard.awk zishrink.awk -MISC= $(AWK_SCRIPTS) zoneinfo2tdf.pl -TZS_YEAR= 2050 -TZS_CUTOFF_FLAG= -c $(TZS_YEAR) -TZS= to$(TZS_YEAR).tzs -TZS_NEW= to$(TZS_YEAR)new.tzs -TZS_DEPS= $(PRIMARY_YDATA) asctime.c localtime.c \ - private.h tzfile.h zdump.c zic.c -# EIGHT_YARDS is just a yard short of the whole ENCHILADA. -EIGHT_YARDS = $(COMMON) $(DOCS) $(SOURCES) $(DATA) $(MISC) tzdata.zi -ENCHILADA = $(EIGHT_YARDS) $(TZS) - -# Consult these files when deciding whether to rebuild the 'version' file. -# This list is not the same as the output of 'git ls-files', since -# .gitignore is not distributed. -VERSION_DEPS= \ - calendars CONTRIBUTING LICENSE Makefile NEWS README \ - africa antarctica asctime.c asia australasia \ - backward backzone \ - checklinks.awk checktab.awk \ - date.1 date.c difftime.c \ - etcetera europe factory iso3166.tab \ - leap-seconds.list leapseconds.awk localtime.c \ - newctime.3 newstrftime.3 newtzset.3 northamerica \ - pacificnew private.h \ - southamerica strftime.c systemv theory.html \ - time2posix.3 tz-art.html tz-how-to.html tz-link.html \ - tzfile.5 tzfile.h tzselect.8 tzselect.ksh \ - workman.sh yearistype.sh \ - zdump.8 zdump.c zic.8 zic.c \ - ziguard.awk zishrink.awk \ - zone.tab zone1970.tab zoneinfo2tdf.pl - -# And for the benefit of csh users on systems that assume the user -# shell should be used to handle commands in Makefiles. . . - -SHELL= /bin/sh - -all: tzselect yearistype zic zdump libtz.a $(TABDATA) \ - vanguard.zi main.zi rearguard.zi - -ALL: all date $(ENCHILADA) - -install: all $(DATA) $(REDO) $(MANS) - mkdir -p '$(DESTDIR)$(BINDIR)' \ - '$(DESTDIR)$(ZDUMPDIR)' '$(DESTDIR)$(ZICDIR)' \ - '$(DESTDIR)$(LIBDIR)' \ - '$(DESTDIR)$(MANDIR)/man3' '$(DESTDIR)$(MANDIR)/man5' \ - '$(DESTDIR)$(MANDIR)/man8' - $(ZIC_INSTALL) -l $(LOCALTIME) \ - `case '$(POSIXRULES)' in ?*) echo '-p';; esac \ - ` $(POSIXRULES) \ - -t '$(DESTDIR)$(TZDEFAULT)' - cp -f $(TABDATA) '$(DESTDIR)$(TZDIR)/.' - cp tzselect '$(DESTDIR)$(BINDIR)/.' - cp zdump '$(DESTDIR)$(ZDUMPDIR)/.' - cp zic '$(DESTDIR)$(ZICDIR)/.' - cp libtz.a '$(DESTDIR)$(LIBDIR)/.' - $(RANLIB) '$(DESTDIR)$(LIBDIR)/libtz.a' - cp -f newctime.3 newtzset.3 '$(DESTDIR)$(MANDIR)/man3/.' - cp -f tzfile.5 '$(DESTDIR)$(MANDIR)/man5/.' - cp -f tzselect.8 zdump.8 zic.8 '$(DESTDIR)$(MANDIR)/man8/.' - -INSTALL: ALL install date.1 - mkdir -p '$(DESTDIR)$(BINDIR)' '$(DESTDIR)$(MANDIR)/man1' - cp date '$(DESTDIR)$(BINDIR)/.' - cp -f date.1 '$(DESTDIR)$(MANDIR)/man1/.' - -version: $(VERSION_DEPS) - { (type git) >/dev/null 2>&1 && \ - V=`git describe --match '[0-9][0-9][0-9][0-9][a-z]*' \ - --abbrev=7 --dirty` || \ - V='$(VERSION)'; } && \ - printf '%s\n' "$$V" >$@.out - mv $@.out $@ - -# These files can be tailored by setting BACKWARD and PACKRATDATA. -vanguard.zi main.zi rearguard.zi: $(DSTDATA_ZI_DEPS) - $(AWK) -v DATAFORM=`expr $@ : '\(.*\).zi'` -f ziguard.awk \ - $(TDATA) $(PACKRATDATA) >$@.out - mv $@.out $@ -# This file has a version comment that attempts to capture any tailoring -# via BACKWARD, DATAFORM, PACKRATDATA, and REDO. -tzdata.zi: $(DATAFORM).zi version zishrink.awk - version=`sed 1q version` && \ - LC_ALL=C $(AWK) \ - -v dataform='$(DATAFORM)' \ - -v deps='$(DSTDATA_ZI_DEPS) zishrink.awk' \ - -v redo='$(REDO)' \ - -v version="$$version" \ - -f zishrink.awk \ - $(DATAFORM).zi >$@.out - mv $@.out $@ - -version.h: version - VERSION=`cat version` && printf '%s\n' \ - 'static char const PKGVERSION[]="($(PACKAGE)) ";' \ - "static char const TZVERSION[]=\"$$VERSION\";" \ - 'static char const REPORT_BUGS_TO[]="$(BUGEMAIL)";' \ - >$@.out - mv $@.out $@ - -zdump: $(TZDOBJS) - $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $(TZDOBJS) $(LDLIBS) - -zic: $(TZCOBJS) - $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $(TZCOBJS) $(LDLIBS) - -yearistype: yearistype.sh - cp yearistype.sh yearistype - chmod +x yearistype - -leapseconds: $(LEAP_DEPS) - $(AWK) -f leapseconds.awk leap-seconds.list >$@.out - mv $@.out $@ - -# Arguments to pass to submakes of install_data. -# They can be overridden by later submake arguments. -INSTALLARGS = \ - BACKWARD='$(BACKWARD)' \ - DESTDIR='$(DESTDIR)' \ - LEAPSECONDS='$(LEAPSECONDS)' \ - PACKRATDATA='$(PACKRATDATA)' \ - TZDEFAULT='$(TZDEFAULT)' \ - TZDIR='$(TZDIR)' \ - YEARISTYPE='$(YEARISTYPE)' \ - ZIC='$(ZIC)' - -INSTALL_DATA_DEPS = zic leapseconds yearistype tzdata.zi - -# 'make install_data' installs one set of TZif files. -install_data: $(INSTALL_DATA_DEPS) - $(ZIC_INSTALL) tzdata.zi - -posix_only: $(INSTALL_DATA_DEPS) - $(MAKE) $(INSTALLARGS) LEAPSECONDS= install_data - -right_only: $(INSTALL_DATA_DEPS) - $(MAKE) $(INSTALLARGS) LEAPSECONDS='-L leapseconds' \ - install_data - -# In earlier versions of this makefile, the other two directories were -# subdirectories of $(TZDIR). However, this led to configuration errors. -# For example, with posix_right under the earlier scheme, -# TZ='right/Australia/Adelaide' got you localtime with leap seconds, -# but gmtime without leap seconds, which led to problems with applications -# like sendmail that subtract gmtime from localtime. -# Therefore, the other two directories are now siblings of $(TZDIR). -# You must replace all of $(TZDIR) to switch from not using leap seconds -# to using them, or vice versa. -right_posix: right_only - rm -fr '$(DESTDIR)$(TZDIR)-leaps' - ln -s '$(TZDIR_BASENAME)' '$(DESTDIR)$(TZDIR)-leaps' || \ - $(MAKE) $(INSTALLARGS) TZDIR='$(TZDIR)-leaps' right_only - $(MAKE) $(INSTALLARGS) TZDIR='$(TZDIR)-posix' posix_only - -posix_right: posix_only - rm -fr '$(DESTDIR)$(TZDIR)-posix' - ln -s '$(TZDIR_BASENAME)' '$(DESTDIR)$(TZDIR)-posix' || \ - $(MAKE) $(INSTALLARGS) TZDIR='$(TZDIR)-posix' posix_only - $(MAKE) $(INSTALLARGS) TZDIR='$(TZDIR)-leaps' right_only - -# This obsolescent rule is present for backwards compatibility with -# tz releases 2014g through 2015g. It should go away eventually. -posix_packrat: $(INSTALL_DATA_DEPS) - $(MAKE) $(INSTALLARGS) PACKRATDATA=backzone posix_only - -zones: $(REDO) - -# dummy.zd is not a real file; it is mentioned here only so that the -# top-level 'make' does not have a syntax error. -ZDS = dummy.zd -# Rule used only by submakes invoked by the $(TZS_NEW) rule. -# It is separate so that GNU 'make -j' can run instances in parallel. -$(ZDS): zdump - ./zdump -i $(TZS_CUTOFF_FLAG) '$(wd)/'$$(expr $@ : '\(.*\).zd') \ - >$@ - -TZS_NEW_DEPS = tzdata.zi zdump zic -$(TZS_NEW): $(TZS_NEW_DEPS) - rm -fr tzs$(TZS_YEAR).dir - mkdir tzs$(TZS_YEAR).dir - $(zic) -d tzs$(TZS_YEAR).dir tzdata.zi - $(AWK) '/^L/{print "Link\t" $$2 "\t" $$3}' \ - tzdata.zi | LC_ALL=C sort >$@.out - wd=`pwd` && \ - x=`$(AWK) '/^Z/{print "tzs$(TZS_YEAR).dir/" $$2 ".zd"}' \ - tzdata.zi \ - | LC_ALL=C sort -t . -k 2,2` && \ - set x $$x && \ - shift && \ - ZDS=$$* && \ - $(MAKE) wd="$$wd" TZS_CUTOFF_FLAG="$(TZS_CUTOFF_FLAG)" \ - ZDS="$$ZDS" $$ZDS && \ - sed 's,^TZ=".*\.dir/,TZ=",' $$ZDS >>$@.out - rm -fr tzs$(TZS_YEAR).dir - mv $@.out $@ - -# If $(TZS) exists but 'make check_tzs' fails, a maintainer should inspect the -# failed output and fix the inconsistency, perhaps by running 'make force_tzs'. -$(TZS): - touch $@ - -force_tzs: $(TZS_NEW) - cp $(TZS_NEW) $(TZS) - -libtz.a: $(LIBOBJS) - rm -f $@ - $(AR) -rc $@ $(LIBOBJS) - $(RANLIB) $@ - -date: $(DATEOBJS) - $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $(DATEOBJS) $(LDLIBS) - -tzselect: tzselect.ksh version - VERSION=`cat version` && sed \ - -e 's|#!/bin/bash|#!$(KSHELL)|g' \ - -e 's|AWK=[^}]*|AWK=$(AWK)|g' \ - -e 's|\(PKGVERSION\)=.*|\1='\''($(PACKAGE)) '\''|' \ - -e 's|\(REPORT_BUGS_TO\)=.*|\1=$(BUGEMAIL)|' \ - -e 's|TZDIR=[^}]*|TZDIR=$(TZDIR)|' \ - -e 's|\(TZVERSION\)=.*|\1='"$$VERSION"'|' \ - <$@.ksh >$@.out - chmod +x $@.out - mv $@.out $@ - -check: check_character_set check_white_space check_links \ - check_name_lengths check_sorted \ - check_tables check_web check_zishrink check_tzs - -check_character_set: $(ENCHILADA) - test ! '$(UTF8_LOCALE)' || \ - ! printf 'A\304\200B\n' | \ - LC_ALL='$(UTF8_LOCALE)' grep -q '^A.B$$' >/dev/null 2>&1 || { \ - LC_ALL='$(UTF8_LOCALE)' && export LC_ALL && \ - sharp='#' && \ - ! grep -Env $(SAFE_LINE) $(MANS) date.1 $(MANTXTS) \ - $(MISC) $(SOURCES) $(WEB_PAGES) \ - CONTRIBUTING LICENSE README \ - version tzdata.zi && \ - ! grep -Env $(SAFE_LINE)'|^UNUSUAL_OK_'$(OK_CHAR)'*$$' \ - Makefile && \ - ! grep -Env $(SAFE_SHARP_LINE) $(TDATA_TO_CHECK) backzone \ - leapseconds yearistype.sh zone.tab && \ - ! grep -Env $(OK_LINE) $(ENCHILADA); \ - } +MAKE=make +SHELL=/bin/bash +PYTHON24=python2.4 +PYTHON25=python2.5 +PYTHON26=python2.6 +PYTHON27=python2.7 +PYTHON31=python3.1 +PYTHON32=python3.2 +PYTHON33=python3.3 +PYTHON34=python3.4 +PYTHON35=python3.5 +PYTHON36=python3.6 +PYTHON37=python3.7 +PYTHON=/usr/bin/python +PYTHON3=/usr/bin/python3 +IANA=./tz +IANA_GIT=https://github.com/eggert/tz.git + +TESTARGS=-vv +TARGET= +#TARGET=Europe/Amsterdam Europe/Moscow W-SU Etc/GMT+2 Atlantic/South_Georgia Europe/Warsaw Europe/Vilnius +#Mideast/Riyadh87 +STYLESHEET=/usr/share/python-docutils/stylesheets/default.css + +all: dist + +check: test_tzinfo test_docs + +build: .stamp-tzinfo + + +dist: build wheels + cd build/dist && mkdir -p ../tarballs && \ + ${PYTHON} setup.py -q sdist --dist-dir ../tarballs \ + --formats=bztar,gztar,zip + +wheels: + cd build/dist && mkdir -p ../tarballs + cd build/dist && ${PYTHON} setup.py -q bdist_wheel --universal --dist-dir=../tarballs + cd build/dist && ${PYTHON3} setup.py -q bdist_wheel --universal --dist-dir=../tarballs + +upload: sign + cd build/dist && ${PYTHON3} setup.py register + twine upload build/tarballs/*.{whl,gz,asc} + +sign: + rm -f build/tarballs/*.asc + for f in build/tarballs/*.{whl,zip,bz2,gz} ; do \ + gpg --detach-sign -a $$f; \ + done + +test: test_lazy test_tzinfo test_docs test_zdump + +lint: .stamp-tzinfo + flake8 --ignore=E402 build/dist gen_*.py test_zdump.py + +clean: + rm -f .stamp-* + rm -rf build/*/* zdump.out + make -C ${IANA} clean + find . -name \*.pyc | xargs rm -f + +test_lazy: .stamp-tzinfo + cd build/dist/pytz/tests \ + && ${PYTHON24} test_lazy.py ${TESTARGS} \ + && ${PYTHON25} test_lazy.py ${TESTARGS} \ + && ${PYTHON26} test_lazy.py ${TESTARGS} \ + && ${PYTHON27} test_lazy.py ${TESTARGS} \ + && ${PYTHON31} test_lazy.py ${TESTARGS} \ + && ${PYTHON32} test_lazy.py ${TESTARGS} \ + && ${PYTHON33} test_lazy.py ${TESTARGS} \ + && ${PYTHON34} test_lazy.py ${TESTARGS} \ + && ${PYTHON35} test_lazy.py ${TESTARGS} \ + && ${PYTHON36} test_lazy.py ${TESTARGS} \ + && ${PYTHON37} test_lazy.py ${TESTARGS} + +test_tzinfo: .stamp-tzinfo + cd build/dist/pytz/tests \ + && ${PYTHON24} test_tzinfo.py ${TESTARGS} \ + && ${PYTHON25} test_tzinfo.py ${TESTARGS} \ + && ${PYTHON26} test_tzinfo.py ${TESTARGS} \ + && ${PYTHON27} test_tzinfo.py ${TESTARGS} \ + && ${PYTHON31} test_tzinfo.py ${TESTARGS} \ + && ${PYTHON32} test_tzinfo.py ${TESTARGS} \ + && ${PYTHON33} test_tzinfo.py ${TESTARGS} \ + && ${PYTHON34} test_tzinfo.py ${TESTARGS} \ + && ${PYTHON35} test_tzinfo.py ${TESTARGS} \ + && ${PYTHON36} test_tzinfo.py ${TESTARGS} \ + && ${PYTHON37} test_tzinfo.py ${TESTARGS} + +test_docs: .stamp-tzinfo + cd build/dist/pytz/tests \ + && ${PYTHON} test_docs.py ${TESTARGS} \ + && ${PYTHON3} test_docs.py ${TESTARGS} + +test_zdump: dist + ${PYTHON} gen_tests.py ${TARGET} && \ + ${PYTHON} test_zdump.py ${TESTARGS} && \ + ${PYTHON3} test_zdump.py ${TESTARGS} + +build/dist/test_zdump.py: .stamp-zoneinfo + +doc: docs + +docs: dist + mkdir -p build/docs/source/.static + mkdir -p build/docs/built + cp src/README.txt build/docs/source/index.txt + cp conf.py build/docs/source/conf.py + sphinx-build build/docs/source build/docs/built + chmod -R og-w build/docs/built + chmod -R a+rX build/docs/built + +upload_docs: upload_docs_pythonhosted upload_docs_sf + +upload_docs_sf: docs + rsync -e ssh -ravP build/docs/built/ \ + web.sourceforge.net:/home/project-web/pytz/htdocs/ + +upload_docs_pythonhosted: docs + cd build/dist \ + && ${PYTHON} setup.py upload_docs --upload-dir=../docs/built + +.stamp-tzinfo: .stamp-zoneinfo gen_tzinfo.py build/etc/zoneinfo/GMT + ${PYTHON} gen_tzinfo.py ${TARGET} + rm -rf build/dist/pytz/zoneinfo + cp -a build/etc/zoneinfo build/dist/pytz/zoneinfo touch $@ -check_white_space: $(ENCHILADA) - patfmt=' \t|[\f\r\v]' && pat=`printf "$$patfmt\\n"` && \ - ! grep -En "$$pat" \ - $$(ls $(ENCHILADA) | grep -Fvx leap-seconds.list) - ! grep -n '[[:space:]]$$' \ - $$(ls $(ENCHILADA) | grep -Fvx leap-seconds.list) - touch $@ - -PRECEDES_FILE_NAME = ^(Zone|Link[[:space:]]+[^[:space:]]+)[[:space:]]+ -FILE_NAME_COMPONENT_TOO_LONG = \ - $(PRECEDES_FILE_NAME)[^[:space:]]*[^/[:space:]]{15} - -check_name_lengths: $(TDATA_TO_CHECK) backzone - ! grep -En '$(FILE_NAME_COMPONENT_TOO_LONG)' \ - $(TDATA_TO_CHECK) backzone - touch $@ - -CHECK_CC_LIST = { n = split($$1,a,/,/); for (i=2; i<=n; i++) print a[1], a[i]; } - -check_sorted: backward backzone iso3166.tab zone.tab zone1970.tab - $(AWK) '/^Link/ {print $$3}' backward | LC_ALL=C sort -cu - $(AWK) '/^Zone/ {print $$2}' backzone | LC_ALL=C sort -cu - touch $@ - -check_links: checklinks.awk $(TDATA_TO_CHECK) tzdata.zi - $(AWK) -f checklinks.awk $(TDATA_TO_CHECK) - $(AWK) -f checklinks.awk tzdata.zi - touch $@ - -check_tables: checktab.awk $(PRIMARY_YDATA) $(ZONETABLES) - for tab in $(ZONETABLES); do \ - $(AWK) -f checktab.awk -v zone_table=$$tab $(PRIMARY_YDATA) \ - || exit; \ - done - touch $@ - -check_tzs: $(TZS) $(TZS_NEW) - if test -s $(TZS); then \ - diff -u $(TZS) $(TZS_NEW); \ - else \ - cp $(TZS_NEW) $(TZS); \ - fi - touch $@ - -check_web: $(CHECK_WEB_PAGES) -check_theory.html: theory.html -check_tz-art.html: tz-art.html -check_tz-link.html: tz-link.html -check_theory.html check_tz-art.html check_tz-link.html: - $(CURL) -sS --url https://validator.w3.org/nu/ -F out=gnu \ - -F file=@$$(expr $@ : 'check_\(.*\)') -o $@.out && \ - test ! -s $@.out || { cat $@.out; exit 1; } - mv $@.out $@ -check_tz-how-to.html: tz-how-to.html - $(VALIDATE_ENV) $(VALIDATE) $(VALIDATE_FLAGS) tz-how-to.html - touch $@ - -# Check that zishrink.awk does not alter the data, and that ziguard.awk -# preserves main-format data. -check_zishrink: check_zishrink_posix check_zishrink_right -check_zishrink_posix check_zishrink_right: \ - zic leapseconds $(PACKRATDATA) $(TDATA) $(DATAFORM).zi tzdata.zi - rm -fr $@.dir $@-t.dir $@-shrunk.dir - mkdir $@.dir $@-t.dir $@-shrunk.dir - case $@ in \ - *_right) leap='-L leapseconds';; \ - *) leap=;; \ - esac && \ - $(ZIC) $$leap -d $@.dir $(DATAFORM).zi && \ - $(ZIC) $$leap -d $@-shrunk.dir tzdata.zi && \ - case $(DATAFORM) in \ - main) \ - $(ZIC) $$leap -d $@-t.dir $(TDATA) && \ - $(AWK) '/^Rule/' $(TDATA) | \ - $(ZIC) $$leap -d $@-t.dir - $(PACKRATDATA) && \ - diff -r $@.dir $@-t.dir;; \ - esac - diff -r $@.dir $@-shrunk.dir - rm -fr $@.dir $@-t.dir $@-shrunk.dir - touch $@ - -clean_misc: - rm -fr check_*.dir - rm -f *.o *.out $(TIME_T_ALTERNATIVES) \ - check_* core typecheck_* \ - date tzselect version.h zdump zic yearistype libtz.a -clean: clean_misc - rm -fr *.dir tzdb-*/ - rm -f *.zi $(TZS_NEW) - -maintainer-clean: clean - @echo 'This command is intended for maintainers to use; it' - @echo 'deletes files that may need special tools to rebuild.' - rm -f leapseconds version $(MANTXTS) $(TZS) *.asc *.tar.* - -names: - @echo $(ENCHILADA) - -public: check check_public $(CHECK_TIME_T_ALTERNATIVES) \ - tarballs signatures - -date.1.txt: date.1 -newctime.3.txt: newctime.3 -newstrftime.3.txt: newstrftime.3 -newtzset.3.txt: newtzset.3 -time2posix.3.txt: time2posix.3 -tzfile.5.txt: tzfile.5 -tzselect.8.txt: tzselect.8 -zdump.8.txt: zdump.8 -zic.8.txt: zic.8 - -$(MANTXTS): workman.sh - LC_ALL=C sh workman.sh `expr $@ : '\(.*\)\.txt$$'` >$@.out - mv $@.out $@ - -# Set the timestamps to those of the git repository, if available, -# and if the files have not changed since then. -# This uses GNU 'touch' syntax 'touch -d@N FILE', -# where N is the number of seconds since 1970. -# If git or GNU 'touch' is absent, don't bother to sync with git timestamps. -# Also, set the timestamp of each prebuilt file like 'leapseconds' -# to be the maximum of the files it depends on. -set-timestamps.out: $(EIGHT_YARDS) - rm -f $@ - if (type git) >/dev/null 2>&1 && \ - files=`git ls-files $(EIGHT_YARDS)` && \ - touch -md @1 test.out; then \ - rm -f test.out && \ - for file in $$files; do \ - if git diff --quiet $$file; then \ - time=`git log -1 --format='tformat:%ct' $$file` && \ - touch -cmd @$$time $$file; \ - else \ - echo >&2 "$$file: warning: does not match repository"; \ - fi || exit; \ - done; \ - fi - touch -cmr `ls -t $(LEAP_DEPS) | sed 1q` leapseconds - for file in `ls $(MANTXTS) | sed 's/\.txt$$//'`; do \ - touch -cmr `ls -t $$file workman.sh | sed 1q` $$file.txt || \ - exit; \ - done - touch -cmr `ls -t $(TZDATA_ZI_DEPS) | sed 1q` tzdata.zi - touch -cmr `ls -t $(VERSION_DEPS) | sed 1q` version - touch $@ -set-tzs-timestamp.out: $(TZS) - touch -cmr `ls -t $(TZS_DEPS) | sed 1q` $(TZS) - touch $@ - -# The zics below ensure that each data file can stand on its own. -# We also do an all-files run to catch links to links. - -check_public: $(VERSION_DEPS) - rm -fr public.dir - mkdir public.dir - ln $(VERSION_DEPS) public.dir - cd public.dir && $(MAKE) CFLAGS='$(GCC_DEBUG_FLAGS)' ALL - for i in $(TDATA_TO_CHECK) public.dir/tzdata.zi; do \ - public.dir/zic -v -d public.dir/zoneinfo $$i 2>&1 || exit; \ - done - public.dir/zic -v -d public.dir/zoneinfo-all $(TDATA_TO_CHECK) - rm -fr public.dir - touch $@ - -# Check that the code works under various alternative -# implementations of time_t. -check_time_t_alternatives: $(TIME_T_ALTERNATIVES) -$(TIME_T_ALTERNATIVES_TAIL): $(TIME_T_ALTERNATIVES_HEAD) -$(TIME_T_ALTERNATIVES): $(VERSION_DEPS) - rm -fr $@.dir - mkdir $@.dir - ln $(VERSION_DEPS) $@.dir - case $@ in \ - int32_t) range=-2147483648,2147483648;; \ - u*) range=0,4294967296;; \ - *) range=-4294967296,4294967296;; \ - esac && \ - wd=`pwd` && \ - zones=`$(AWK) '/^[^#]/ { print $$3 }' <zone1970.tab` && \ - if test $@ = $(TIME_T_ALTERNATIVES_HEAD); then \ - range_target=; \ - else \ - range_target=to$$range.tzs; \ - fi && \ - (cd $@.dir && \ - $(MAKE) TOPDIR="$$wd/$@.dir" \ - CFLAGS='$(CFLAGS) -Dtime_tz='"'$@'" \ - REDO='$(REDO)' \ - D=$$wd/$@.dir \ - TZS_YEAR="$$range" TZS_CUTOFF_FLAG="-t $$range" \ - install $$range_target) && \ - test $@ = $(TIME_T_ALTERNATIVES_HEAD) || { \ - (cd $(TIME_T_ALTERNATIVES_HEAD).dir && \ - $(MAKE) TOPDIR="$$wd/$@.dir" \ - TZS_YEAR="$$range" TZS_CUTOFF_FLAG="-t $$range" \ - D=$$wd/$@.dir \ - to$$range.tzs) && \ - diff -u $(TIME_T_ALTERNATIVES_HEAD).dir/to$$range.tzs \ - $@.dir/to$$range.tzs && \ - if diff -q Makefile Makefile 2>/dev/null; then \ - quiet_option='-q'; \ - else \ - quiet_option=''; \ - fi && \ - diff $$quiet_option -r $(TIME_T_ALTERNATIVES_HEAD).dir/etc \ - $@.dir/etc && \ - diff $$quiet_option -r \ - $(TIME_T_ALTERNATIVES_HEAD).dir/usr/share \ - $@.dir/usr/share; \ - } - touch $@ - -TRADITIONAL_ASC = \ - tzcode$(VERSION).tar.gz.asc \ - tzdata$(VERSION).tar.gz.asc -REARGUARD_ASC = \ - tzdata$(VERSION)-rearguard.tar.gz.asc -ALL_ASC = $(TRADITIONAL_ASC) $(REARGUARD_ASC) \ - tzdb-$(VERSION).tar.lz.asc - -tarballs rearguard_tarballs traditional_tarballs \ -signatures rearguard_signatures traditional_signatures: \ - version set-timestamps.out rearguard.zi - VERSION=`cat version` && \ - $(MAKE) VERSION="$$VERSION" $@_version - -# These *_version rules are intended for use if VERSION is set by some -# other means. Ordinarily these rules are used only by the above -# non-_version rules, which set VERSION on the 'make' command line. -tarballs_version: traditional_tarballs_version rearguard_tarballs_version \ - tzdb-$(VERSION).tar.lz -rearguard_tarballs_version: \ - tzdata$(VERSION)-rearguard.tar.gz -traditional_tarballs_version: \ - tzcode$(VERSION).tar.gz tzdata$(VERSION).tar.gz -signatures_version: $(ALL_ASC) -rearguard_signatures_version: $(REARGUARD_ASC) -traditional_signatures_version: $(TRADITIONAL_ASC) - -tzcode$(VERSION).tar.gz: set-timestamps.out - LC_ALL=C && export LC_ALL && \ - tar $(TARFLAGS) -cf - \ - $(COMMON) $(DOCS) $(SOURCES) | \ - gzip $(GZIPFLAGS) >$@.out - mv $@.out $@ - -tzdata$(VERSION).tar.gz: set-timestamps.out - LC_ALL=C && export LC_ALL && \ - tar $(TARFLAGS) -cf - $(COMMON) $(DATA) $(MISC) | \ - gzip $(GZIPFLAGS) >$@.out - mv $@.out $@ - -tzdata$(VERSION)-rearguard.tar.gz: rearguard.zi set-timestamps.out - rm -fr tzdata$(VERSION)-rearguard.dir - mkdir tzdata$(VERSION)-rearguard.dir - ln $(COMMON) $(DATA) $(MISC) tzdata$(VERSION)-rearguard.dir - cd tzdata$(VERSION)-rearguard.dir && \ - rm -f $(TDATA) $(PACKRATDATA) version - for f in $(TDATA) $(PACKRATDATA); do \ - rearf=tzdata$(VERSION)-rearguard.dir/$$f; \ - $(AWK) -v DATAFORM=rearguard -f ziguard.awk $$f >$$rearf && \ - touch -cmr `ls -t ziguard.awk $$f` $$rearf || exit; \ - done - sed '1s/$$/-rearguard/' \ - <version >tzdata$(VERSION)-rearguard.dir/version - touch -cmr version tzdata$(VERSION)-rearguard.dir/version - LC_ALL=C && export LC_ALL && \ - (cd tzdata$(VERSION)-rearguard.dir && \ - tar $(TARFLAGS) -cf - $(COMMON) $(DATA) $(MISC) | \ - gzip $(GZIPFLAGS)) >$@.out - mv $@.out $@ - -tzdb-$(VERSION).tar.lz: set-timestamps.out set-tzs-timestamp.out - rm -fr tzdb-$(VERSION) - mkdir tzdb-$(VERSION) - ln $(ENCHILADA) tzdb-$(VERSION) - touch -cmr `ls -t tzdb-$(VERSION)/* | sed 1q` tzdb-$(VERSION) - LC_ALL=C && export LC_ALL && \ - tar $(TARFLAGS) -cf - tzdb-$(VERSION) | lzip -9 >$@.out - mv $@.out $@ - -tzcode$(VERSION).tar.gz.asc: tzcode$(VERSION).tar.gz -tzdata$(VERSION).tar.gz.asc: tzdata$(VERSION).tar.gz -tzdata$(VERSION)-rearguard.tar.gz.asc: tzdata$(VERSION)-rearguard.tar.gz -tzdb-$(VERSION).tar.lz.asc: tzdb-$(VERSION).tar.lz -$(ALL_ASC): - $(GPG) --armor --detach-sign $? - -TYPECHECK_CFLAGS = $(CFLAGS) -DTYPECHECK -D__time_t_defined -D_TIME_T -typecheck: typecheck_long_long typecheck_unsigned -typecheck_long_long typecheck_unsigned: $(VERSION_DEPS) - rm -fr $@.dir - mkdir $@.dir - ln $(VERSION_DEPS) $@.dir - cd $@.dir && \ - case $@ in \ - *_long_long) i="long long";; \ - *_unsigned ) i="unsigned" ;; \ - esac && \ - typecheck_cflags='' && \ - $(MAKE) \ - CFLAGS="$(TYPECHECK_CFLAGS) \"-Dtime_t=$$i\"" \ - TOPDIR="`pwd`" \ - install - $@.dir/zdump -i -c 1970,1971 Europe/Rome - touch $@ - -zonenames: tzdata.zi - @$(AWK) '/^Z/ { print $$2 } /^L/ { print $$3 }' tzdata.zi +.stamp-zoneinfo: + ${MAKE} -C ${IANA} TOPDIR=`pwd`/build USRDIR= USRSHAREDIR=etc install + # Break hard links, working around http://bugs.python.org/issue8876. + for d in zoneinfo zoneinfo-leaps zoneinfo-posix; do \ + rm -rf `pwd`/build/etc/$$d.tmp; \ + rsync -a `pwd`/build/etc/$$d/ `pwd`/build/etc/$$d.tmp; \ + rm -rf `pwd`/build/etc/$$d; \ + mv `pwd`/build/etc/$$d.tmp `pwd`/build/etc/$$d; \ + done + touch $@ -asctime.o: private.h tzfile.h -date.o: private.h -difftime.o: private.h -localtime.o: private.h tzfile.h -strftime.o: private.h tzfile.h -zdump.o: version.h -zic.o: private.h tzfile.h version.h +build/dist/locales/pytz.pot: .stamp-tzinfo + @: #${PYTHON} gen_pot.py build/dist/pytz/locales/pytz.pot -.KEEP_STATE: +# cd build/dist; mkdir locales; \ +# pygettext --extract-all --no-location \ +# --default-domain=pytz --output-dir=locales -.PHONY: ALL INSTALL all -.PHONY: check check_time_t_alternatives -.PHONY: check_web check_zishrink -.PHONY: clean clean_misc dummy.zd force_tzs -.PHONY: install install_data maintainer-clean names -.PHONY: posix_only posix_packrat posix_right public -.PHONY: rearguard_signatures rearguard_signatures_version -.PHONY: rearguard_tarballs rearguard_tarballs_version -.PHONY: right_only right_posix signatures signatures_version -.PHONY: tarballs tarballs_version -.PHONY: traditional_signatures traditional_signatures_version -.PHONY: traditional_tarballs traditional_tarballs_version -.PHONY: typecheck -.PHONY: zonenames zones -.PHONY: $(ZDS) +# Switch to using a git subtree of https://github.com/eggert/tz +# +# IANA_URL=http://www.iana.org/time-zones/repository +# +# sync: +# cd elsie.nci.nih.gov && \ +# rm -f tz{code,data}-latest.tar.gz{,.asc} && \ +# wget -S ${IANA_URL}/tzcode-latest.tar.gz && \ +# wget -S ${IANA_URL}/tzcode-latest.tar.gz.asc && \ +# gpg --verify tzcode-latest.tar.gz.asc tzcode-latest.tar.gz && \ +# wget -S ${IANA_URL}/tzdata-latest.tar.gz && \ +# wget -S ${IANA_URL}/tzdata-latest.tar.gz.asc && \ +# gpg --verify tzdata-latest.tar.gz.asc tzdata-latest.tar.gz && \ +# cd src && \ +# tar xzf ../tzcode-latest.tar.gz && \ +# tar xzf ../tzdata-latest.tar.gz && \ +# echo Done + +sync: _sync clean + +_sync: + if [ -n "$(TAG)" ]; then \ + git subtree pull --prefix=tz --squash $(IANA_GIT) $(TAG) \ + -m "IANA $(TAG)"; \ + else \ + echo "Usage: make sync TAG=2016f"; \ + fi + +.PHONY: all check dist test test_tzinfo test_docs test_zdump wheels build clean sync _sync diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..f63a900 --- /dev/null +++ b/README.txt @@ -0,0 +1,14 @@ + +Brings the IANA tz database into Python. This library allows accurate and +cross platform timezone calculations. + +More information in src/README.txt + +Release process; + + 1) Untar upstream tarballs into elsie/src + 2) Update VERSION & OLSON_VERSION in src/pytz/__init__.py, and EXPECTED_VERSION in + src/pytz/tests/test_tzinfo.py + 3) make test + 4) make dist + @@ -0,0 +1,182 @@ +# -*- coding: utf-8 -*- +# +# pytz documentation build configuration file, created by +# sphinx-quickstart on Sat Oct 11 19:39:58 2008. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# The contents of this file are pickled, so don't put values in the namespace +# that aren't pickleable (module imports are okay, they're removed automatically). +# +# All configuration values have a default value; values that are commented out +# serve to show the default value. + +import sys, os.path +sys.path.insert(0, os.path.join( + os.path.dirname(__file__), os.pardir, os.pardir, 'dist')) +import pytz + +# If your extensions are in another directory, add it here. If the directory +# is relative to the documentation root, use os.path.abspath to make it +# absolute, like shown here. +#sys.path.append(os.path.abspath('some/directory')) + +# General configuration +# --------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['.templates'] + +# The suffix of source filenames. +source_suffix = '.txt' + +# The master toctree document. +master_doc = 'index' + +# General substitutions. +project = u'pytz' +copyright = u'2008, Stuart Bishop' + +# The default replacements for |version| and |release|, also used in various +# other places throughout the built documents. +# +# The short X.Y version. +version = pytz.__version__ +# The full version, including alpha/beta/rc tags. +release = pytz.__version__ + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directories, that shouldn't be searched +# for source files. +exclude_trees = ['.build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + + +# Options for HTML output +# ----------------------- + +# The style sheet to use for HTML and HTML Help pages. A file of that name +# must exist either in Sphinx' static/ path, or in one of the custom paths +# given in html_static_path. +html_style = 'default.css' + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['.static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +html_use_modindex = False + +# If false, no index is generated. +html_use_index = False + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, the reST sources are included in the HTML build as _sources/<name>. +#html_copy_source = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +html_file_suffix = '.html' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'pytzdoc' + + +# Options for LaTeX output +# ------------------------ + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, document class [howto/manual]). +latex_documents = [ + ('index', 'pytz.tex', u'pytz Documentation', + u'Stuart Bishop', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/deps.txt b/deps.txt new file mode 100644 index 0000000..ecd457a --- /dev/null +++ b/deps.txt @@ -0,0 +1,18 @@ +dev container setup cheat sheet +------------------------------- + +locale-gen +add-apt-repository ppa:deadsnakes/ppa + +apt update +apt install tox bzr build-essential twine python-all python-all-dev python3-all python3-all-dev python3-docutils python3-sphinx python3-flake8 python-flake8 python2.4-complete python2.5-complete python2.6-complete python3.1-complete python3.2-complete python3.3-complete python3.4-complete python3.5 python3.5-dev python3.7 python3.7-dev python-wheel python3-wheel python-pip python3-pip + + +wget https://raw.githubusercontent.com/pypa/setuptools/bootstrap-py24/ez_setup.py -O - | python2.4 +wget https://raw.githubusercontent.com/pypa/setuptools/bootstrap-py24/ez_setup.py -O - | python2.5 +wget https://bootstrap.pypa.io/ez_setup.py -O - | python2.6 +wget https://bootstrap.pypa.io/ez_setup.py -O - | python3.1 +wget https://bootstrap.pypa.io/ez_setup.py -O - | python3.2 +wget https://bootstrap.pypa.io/ez_setup.py -O - | python3.3 +wget https://bootstrap.pypa.io/ez_setup.py -O - | python3.4 + diff --git a/gen_pot.py b/gen_pot.py new file mode 100644 index 0000000..a2358d0 --- /dev/null +++ b/gen_pot.py @@ -0,0 +1,38 @@ +import sys +import os.path +import time +from gen_tzinfo import allzones + +from pytz import __version__ + +boilerplate = r"""msgid "" +msgstr "" +"Project-Id-Version: pytz %s\n" +"POT-Creation-Date: %s\n" +"Content-Type: text/plain; charset=UTF-8\n" + +""" % ( + __version__, + time.strftime('%Y-%m-%d %H:%M+UTC', time.gmtime(time.time())) +) + + +def main(): + assert len(sys.argv) == 2, 'Output file not specified on command line' + pot_file_name = sys.argv[1] + + if not os.path.exists(os.path.dirname(pot_file_name)): + os.makedirs(os.path.dirname(pot_file_name)) + + pot = open(pot_file_name, 'wb') + + print >> pot, boilerplate + + for zone in allzones(): + print >> pot, 'msgid "%s"' % zone + print >> pot, 'msgstr ""' + print >> pot + + +if __name__ == '__main__': + main() diff --git a/gen_tests.py b/gen_tests.py new file mode 100644 index 0000000..cff1204 --- /dev/null +++ b/gen_tests.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# -*- coding: ascii -*- +''' +$Id: gen_tests.py,v 1.15 2005/01/07 04:51:30 zenzen Exp $ +''' + +import os +import os.path +import subprocess +import sys +from gen_tzinfo import allzones +import gen_tzinfo + +zdump = os.path.abspath(os.path.join( + os.path.dirname(__file__), 'build', 'bin', 'zdump' +)) + + +def main(): + dest_dir = os.path.abspath(os.path.join(os.path.dirname(__file__))) + + datf = open(os.path.join(dest_dir, 'zdump.out'), 'w') + + for zone in allzones(): + print('Collecting zdump(1) output for %s in zdump.out' % (zone,)) + # We don't yet support v2 format tzfile(5) files, so limit + # the daterange we test against - zdump understands v2 format + # files and will output historical records we can't cope with + # otherwise. + command = [zdump, '-v', '-c', '1902,2038', zone] + zd_out = subprocess.check_output(command) + # Skip bogus output on 64bit architectures, per Bug #213816 + lines = [ + line.strip() for line in zd_out.splitlines() + if not line.decode('utf-8').strip().endswith('NULL')] + + for line in lines: + print >> datf, line + datf.flush() + datf.close() + +if __name__ == '__main__': + try: + gen_tzinfo.target = sys.argv[1:] + except IndexError: + gen_tzinfo.target = None + main() + +# vim: set filetype=python ts=4 sw=4 et diff --git a/gen_tzinfo.py b/gen_tzinfo.py new file mode 100644 index 0000000..d06fa0e --- /dev/null +++ b/gen_tzinfo.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python +''' +$Id: gen_tzinfo.py,v 1.21 2005/02/15 20:21:38 zenzen Exp $ +''' +import sys +import os +import os.path +import shutil + +from glob import glob +from pprint import pprint +import re + +zoneinfo = os.path.abspath(os.path.join( + os.path.dirname(__file__), 'build', 'etc', 'zoneinfo' +)) + + +def allzones(): + ''' Return all available tzfile(5) files in the zoneinfo database ''' + zones = [] + for dirpath, dirnames, filenames in os.walk(zoneinfo): + for f in filenames: + p = os.path.join(dirpath, f) + if open(p, 'rb').read(4) == 'TZif': + zones.append(p) + stripnum = len(os.path.commonprefix(zones)) + zones = [z[stripnum:] for z in zones] + + if target: + wanted = target + ['US/Eastern', 'UTC'] + zones = [z for z in zones if z in wanted] + # Does not cope with Riyadh87-89 - it appears this region went + # on solar time during this period and their DST offset changed + # minute to minute (the Olson database could only capture a precision + # of 5 seconds because of way too many zone changes, so the data isn't + # 100% accurate anyway). + # 'Factory' and 'localtime' appear to be Olson reference code specific + # and are skipped + zones = [z for z in zones if 'Riyadh8' not in z and z not in [ + 'Factory', 'localtime', 'posixrules']] + zones.sort() + return zones + + +def links(): + '''Mapping of alias -> canonical name''' + l = {} + olson_src_files = glob('tz/*') + assert olson_src_files, 'No src files' + for filename in olson_src_files: + # Filenames containing a '.' are not data files. + if '.' in os.path.basename(filename): + continue + for line in open(filename): + if line.strip().startswith('#') or not line.strip(): + continue + match = re.search(r'^\s*Link\s+([\w/\-]+)\s+([\w/\-]+)', line) + if match is not None: + new_name = match.group(1) + old_name = match.group(2) + l[old_name] = new_name + else: + assert not line.startswith('Link'), line + assert 'US/Pacific-New' in l, 'US/Pacific-New should be in links()' + return l + + +def dupe_src(destdir): + ''' Copy ./src to our dest directory ''' + if not os.path.isdir(destdir): + os.makedirs(destdir) + for f in glob(os.path.join('src', '*')): + if not os.path.isdir(f): + shutil.copy(f, destdir) + + destdir = os.path.join(destdir, 'pytz') + if not os.path.isdir(destdir): + os.makedirs(destdir) + for f in glob(os.path.join('src', 'pytz', '*')): + if not os.path.isdir(f): + shutil.copy(f, destdir) + + destdir = os.path.join(destdir, 'tests') + if not os.path.isdir(destdir): + os.makedirs(destdir) + for f in glob(os.path.join('src', 'pytz', 'tests', '*')): + if not os.path.isdir(f): + shutil.copy(f, destdir) + + +def add_allzones(filename): + ''' Append a list of all know timezones to the end of the file ''' + outf = open(filename, 'a') + + obsolete_zones = links().keys() + + # Calculate 'common' timezones as best we can. We start with all + # timezones, strip out the legacy noise, and any name linked to + # a more canonical name (eg. Asia/Singapore is preferred to just + # Singapore) + cz = [ + z for z in allzones() + if (z not in obsolete_zones and + '/' in z and + not z.startswith('SystemV/') and + not z.startswith('Etc/'))] + # And extend our list manually with stuff we think deserves to be + # labelled 'common'. + cz.extend([ + 'UTC', 'GMT', 'US/Eastern', 'US/Pacific', 'US/Mountain', + 'US/Central', 'US/Arizona', 'US/Hawaii', 'US/Alaska', + # Canadian timezones per Bug #506341 + 'Canada/Newfoundland', 'Canada/Atlantic', 'Canada/Eastern', + 'Canada/Central', 'Canada/Mountain', 'Canada/Pacific']) + # And extend out list with all preferred country timezones. + zone_tab = open(os.path.join(zoneinfo, 'zone.tab'), 'r') + for line in zone_tab: + if line.startswith('#'): + continue + code, coordinates, zone = line.split(None, 4)[:3] + if zone not in cz: + cz.append(zone) + cz.sort() + + print >> outf, 'all_timezones = \\' + pprint(sorted(allzones()), outf) + print >> outf, '''all_timezones = LazyList( + tz for tz in all_timezones if resource_exists(tz)) + ''' + print >> outf, 'all_timezones_set = LazySet(all_timezones)' + print >> outf, '_all_timezones_lower_to_standard = dict((tz.lower(), tz) for tz in all_timezones)' + + print >> outf, 'common_timezones = \\' + pprint(cz, outf) + print >> outf, '''common_timezones = LazyList( + tz for tz in common_timezones if tz in all_timezones) + ''' + print >> outf, 'common_timezones_set = LazySet(common_timezones)' + + outf.close() + + +def main(destdir): + _destdir = os.path.join(os.path.abspath(destdir), 'dist') + + dupe_src(_destdir) + add_allzones(os.path.join(_destdir, 'pytz', '__init__.py')) + +target = None +if __name__ == '__main__': + try: + target = sys.argv[1:] + except IndexError: + target = None + main('build') diff --git a/src/LICENSE.txt b/src/LICENSE.txt new file mode 100644 index 0000000..7c901fd --- /dev/null +++ b/src/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2003-2018 Stuart Bishop <stuart@stuartbishop.net> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/MANIFEST.in b/src/MANIFEST.in new file mode 100644 index 0000000..fda34e2 --- /dev/null +++ b/src/MANIFEST.in @@ -0,0 +1,5 @@ +include *.txt setup.py +recursive-include pytz *.py +#recursive-include pytz *.pot +graft pytz/zoneinfo +#exclude test_zdump.py diff --git a/src/README.txt b/src/README.txt new file mode 100644 index 0000000..b37acf0 --- /dev/null +++ b/src/README.txt @@ -0,0 +1,582 @@ +pytz - World Timezone Definitions for Python +============================================ + +:Author: Stuart Bishop <stuart@stuartbishop.net> + +Introduction +~~~~~~~~~~~~ + +pytz brings the Olson tz database into Python. This library allows +accurate and cross platform timezone calculations using Python 2.4 +or higher. It also solves the issue of ambiguous times at the end +of daylight saving time, which you can read more about in the Python +Library Reference (``datetime.tzinfo``). + +Almost all of the Olson timezones are supported. + +.. note:: + + This library differs from the documented Python API for + tzinfo implementations; if you want to create local wallclock + times you need to use the ``localize()`` method documented in this + document. In addition, if you perform date arithmetic on local + times that cross DST boundaries, the result may be in an incorrect + timezone (ie. subtract 1 minute from 2002-10-27 1:00 EST and you get + 2002-10-27 0:59 EST instead of the correct 2002-10-27 1:59 EDT). A + ``normalize()`` method is provided to correct this. Unfortunately these + issues cannot be resolved without modifying the Python datetime + implementation (see PEP-431). + + +Installation +~~~~~~~~~~~~ + +This package can either be installed using ``pip`` or from a tarball using the +standard Python distutils. + +If you are installing using ``pip``, you don't need to download anything as the +latest version will be downloaded for you from PyPI:: + + pip install pytz + +If you are installing from a tarball, run the following command as an +administrative user:: + + python setup.py install + + +Example & Usage +~~~~~~~~~~~~~~~ + +Localized times and date arithmetic +----------------------------------- + +>>> from datetime import datetime, timedelta +>>> from pytz import timezone +>>> import pytz +>>> utc = pytz.utc +>>> utc.zone +'UTC' +>>> eastern = timezone('US/Eastern') +>>> eastern.zone +'US/Eastern' +>>> amsterdam = timezone('Europe/Amsterdam') +>>> fmt = '%Y-%m-%d %H:%M:%S %Z%z' + +This library only supports two ways of building a localized time. The +first is to use the ``localize()`` method provided by the pytz library. +This is used to localize a naive datetime (datetime with no timezone +information): + +>>> loc_dt = eastern.localize(datetime(2002, 10, 27, 6, 0, 0)) +>>> print(loc_dt.strftime(fmt)) +2002-10-27 06:00:00 EST-0500 + +The second way of building a localized time is by converting an existing +localized time using the standard ``astimezone()`` method: + +>>> ams_dt = loc_dt.astimezone(amsterdam) +>>> ams_dt.strftime(fmt) +'2002-10-27 12:00:00 CET+0100' + +Unfortunately using the tzinfo argument of the standard datetime +constructors ''does not work'' with pytz for many timezones. + +>>> datetime(2002, 10, 27, 12, 0, 0, tzinfo=amsterdam).strftime(fmt) # /!\ Does not work this way! +'2002-10-27 12:00:00 LMT+0020' + +It is safe for timezones without daylight saving transitions though, such +as UTC: + +>>> datetime(2002, 10, 27, 12, 0, 0, tzinfo=pytz.utc).strftime(fmt) # /!\ Not recommended except for UTC +'2002-10-27 12:00:00 UTC+0000' + +The preferred way of dealing with times is to always work in UTC, +converting to localtime only when generating output to be read +by humans. + +>>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc) +>>> loc_dt = utc_dt.astimezone(eastern) +>>> loc_dt.strftime(fmt) +'2002-10-27 01:00:00 EST-0500' + +This library also allows you to do date arithmetic using local +times, although it is more complicated than working in UTC as you +need to use the ``normalize()`` method to handle daylight saving time +and other timezone transitions. In this example, ``loc_dt`` is set +to the instant when daylight saving time ends in the US/Eastern +timezone. + +>>> before = loc_dt - timedelta(minutes=10) +>>> before.strftime(fmt) +'2002-10-27 00:50:00 EST-0500' +>>> eastern.normalize(before).strftime(fmt) +'2002-10-27 01:50:00 EDT-0400' +>>> after = eastern.normalize(before + timedelta(minutes=20)) +>>> after.strftime(fmt) +'2002-10-27 01:10:00 EST-0500' + +Creating local times is also tricky, and the reason why working with +local times is not recommended. Unfortunately, you cannot just pass +a ``tzinfo`` argument when constructing a datetime (see the next +section for more details) + +>>> dt = datetime(2002, 10, 27, 1, 30, 0) +>>> dt1 = eastern.localize(dt, is_dst=True) +>>> dt1.strftime(fmt) +'2002-10-27 01:30:00 EDT-0400' +>>> dt2 = eastern.localize(dt, is_dst=False) +>>> dt2.strftime(fmt) +'2002-10-27 01:30:00 EST-0500' + +Converting between timezones is more easily done, using the +standard astimezone method. + +>>> utc_dt = utc.localize(datetime.utcfromtimestamp(1143408899)) +>>> utc_dt.strftime(fmt) +'2006-03-26 21:34:59 UTC+0000' +>>> au_tz = timezone('Australia/Sydney') +>>> au_dt = utc_dt.astimezone(au_tz) +>>> au_dt.strftime(fmt) +'2006-03-27 08:34:59 AEDT+1100' +>>> utc_dt2 = au_dt.astimezone(utc) +>>> utc_dt2.strftime(fmt) +'2006-03-26 21:34:59 UTC+0000' +>>> utc_dt == utc_dt2 +True + +You can take shortcuts when dealing with the UTC side of timezone +conversions. ``normalize()`` and ``localize()`` are not really +necessary when there are no daylight saving time transitions to +deal with. + +>>> utc_dt = datetime.utcfromtimestamp(1143408899).replace(tzinfo=utc) +>>> utc_dt.strftime(fmt) +'2006-03-26 21:34:59 UTC+0000' +>>> au_tz = timezone('Australia/Sydney') +>>> au_dt = au_tz.normalize(utc_dt.astimezone(au_tz)) +>>> au_dt.strftime(fmt) +'2006-03-27 08:34:59 AEDT+1100' +>>> utc_dt2 = au_dt.astimezone(utc) +>>> utc_dt2.strftime(fmt) +'2006-03-26 21:34:59 UTC+0000' + + +``tzinfo`` API +-------------- + +The ``tzinfo`` instances returned by the ``timezone()`` function have +been extended to cope with ambiguous times by adding an ``is_dst`` +parameter to the ``utcoffset()``, ``dst()`` && ``tzname()`` methods. + +>>> tz = timezone('America/St_Johns') + +>>> normal = datetime(2009, 9, 1) +>>> ambiguous = datetime(2009, 10, 31, 23, 30) + +The ``is_dst`` parameter is ignored for most timestamps. It is only used +during DST transition ambiguous periods to resolve that ambiguity. + +>>> tz.utcoffset(normal, is_dst=True) +datetime.timedelta(-1, 77400) +>>> tz.dst(normal, is_dst=True) +datetime.timedelta(0, 3600) +>>> tz.tzname(normal, is_dst=True) +'NDT' + +>>> tz.utcoffset(ambiguous, is_dst=True) +datetime.timedelta(-1, 77400) +>>> tz.dst(ambiguous, is_dst=True) +datetime.timedelta(0, 3600) +>>> tz.tzname(ambiguous, is_dst=True) +'NDT' + +>>> tz.utcoffset(normal, is_dst=False) +datetime.timedelta(-1, 77400) +>>> tz.dst(normal, is_dst=False) +datetime.timedelta(0, 3600) +>>> tz.tzname(normal, is_dst=False) +'NDT' + +>>> tz.utcoffset(ambiguous, is_dst=False) +datetime.timedelta(-1, 73800) +>>> tz.dst(ambiguous, is_dst=False) +datetime.timedelta(0) +>>> tz.tzname(ambiguous, is_dst=False) +'NST' + +If ``is_dst`` is not specified, ambiguous timestamps will raise +an ``pytz.exceptions.AmbiguousTimeError`` exception. + +>>> tz.utcoffset(normal) +datetime.timedelta(-1, 77400) +>>> tz.dst(normal) +datetime.timedelta(0, 3600) +>>> tz.tzname(normal) +'NDT' + +>>> import pytz.exceptions +>>> try: +... tz.utcoffset(ambiguous) +... except pytz.exceptions.AmbiguousTimeError: +... print('pytz.exceptions.AmbiguousTimeError: %s' % ambiguous) +pytz.exceptions.AmbiguousTimeError: 2009-10-31 23:30:00 +>>> try: +... tz.dst(ambiguous) +... except pytz.exceptions.AmbiguousTimeError: +... print('pytz.exceptions.AmbiguousTimeError: %s' % ambiguous) +pytz.exceptions.AmbiguousTimeError: 2009-10-31 23:30:00 +>>> try: +... tz.tzname(ambiguous) +... except pytz.exceptions.AmbiguousTimeError: +... print('pytz.exceptions.AmbiguousTimeError: %s' % ambiguous) +pytz.exceptions.AmbiguousTimeError: 2009-10-31 23:30:00 + + +Problems with Localtime +~~~~~~~~~~~~~~~~~~~~~~~ + +The major problem we have to deal with is that certain datetimes +may occur twice in a year. For example, in the US/Eastern timezone +on the last Sunday morning in October, the following sequence +happens: + + - 01:00 EDT occurs + - 1 hour later, instead of 2:00am the clock is turned back 1 hour + and 01:00 happens again (this time 01:00 EST) + +In fact, every instant between 01:00 and 02:00 occurs twice. This means +that if you try and create a time in the 'US/Eastern' timezone +the standard datetime syntax, there is no way to specify if you meant +before of after the end-of-daylight-saving-time transition. Using the +pytz custom syntax, the best you can do is make an educated guess: + +>>> loc_dt = eastern.localize(datetime(2002, 10, 27, 1, 30, 00)) +>>> loc_dt.strftime(fmt) +'2002-10-27 01:30:00 EST-0500' + +As you can see, the system has chosen one for you and there is a 50% +chance of it being out by one hour. For some applications, this does +not matter. However, if you are trying to schedule meetings with people +in different timezones or analyze log files it is not acceptable. + +The best and simplest solution is to stick with using UTC. The pytz +package encourages using UTC for internal timezone representation by +including a special UTC implementation based on the standard Python +reference implementation in the Python documentation. + +The UTC timezone unpickles to be the same instance, and pickles to a +smaller size than other pytz tzinfo instances. The UTC implementation +can be obtained as pytz.utc, pytz.UTC, or pytz.timezone('UTC'). + +>>> import pickle, pytz +>>> dt = datetime(2005, 3, 1, 14, 13, 21, tzinfo=utc) +>>> naive = dt.replace(tzinfo=None) +>>> p = pickle.dumps(dt, 1) +>>> naive_p = pickle.dumps(naive, 1) +>>> len(p) - len(naive_p) +17 +>>> new = pickle.loads(p) +>>> new == dt +True +>>> new is dt +False +>>> new.tzinfo is dt.tzinfo +True +>>> pytz.utc is pytz.UTC is pytz.timezone('UTC') +True + +Note that some other timezones are commonly thought of as the same (GMT, +Greenwich, Universal, etc.). The definition of UTC is distinct from these +other timezones, and they are not equivalent. For this reason, they will +not compare the same in Python. + +>>> utc == pytz.timezone('GMT') +False + +See the section `What is UTC`_, below. + +If you insist on working with local times, this library provides a +facility for constructing them unambiguously: + +>>> loc_dt = datetime(2002, 10, 27, 1, 30, 00) +>>> est_dt = eastern.localize(loc_dt, is_dst=True) +>>> edt_dt = eastern.localize(loc_dt, is_dst=False) +>>> print(est_dt.strftime(fmt) + ' / ' + edt_dt.strftime(fmt)) +2002-10-27 01:30:00 EDT-0400 / 2002-10-27 01:30:00 EST-0500 + +If you pass None as the is_dst flag to localize(), pytz will refuse to +guess and raise exceptions if you try to build ambiguous or non-existent +times. + +For example, 1:30am on 27th Oct 2002 happened twice in the US/Eastern +timezone when the clocks where put back at the end of Daylight Saving +Time: + +>>> dt = datetime(2002, 10, 27, 1, 30, 00) +>>> try: +... eastern.localize(dt, is_dst=None) +... except pytz.exceptions.AmbiguousTimeError: +... print('pytz.exceptions.AmbiguousTimeError: %s' % dt) +pytz.exceptions.AmbiguousTimeError: 2002-10-27 01:30:00 + +Similarly, 2:30am on 7th April 2002 never happened at all in the +US/Eastern timezone, as the clocks where put forward at 2:00am skipping +the entire hour: + +>>> dt = datetime(2002, 4, 7, 2, 30, 00) +>>> try: +... eastern.localize(dt, is_dst=None) +... except pytz.exceptions.NonExistentTimeError: +... print('pytz.exceptions.NonExistentTimeError: %s' % dt) +pytz.exceptions.NonExistentTimeError: 2002-04-07 02:30:00 + +Both of these exceptions share a common base class to make error handling +easier: + +>>> isinstance(pytz.AmbiguousTimeError(), pytz.InvalidTimeError) +True +>>> isinstance(pytz.NonExistentTimeError(), pytz.InvalidTimeError) +True + + +A special case is where countries change their timezone definitions +with no daylight savings time switch. For example, in 1915 Warsaw +switched from Warsaw time to Central European time with no daylight savings +transition. So at the stroke of midnight on August 5th 1915 the clocks +were wound back 24 minutes creating an ambiguous time period that cannot +be specified without referring to the timezone abbreviation or the +actual UTC offset. In this case midnight happened twice, neither time +during a daylight saving time period. pytz handles this transition by +treating the ambiguous period before the switch as daylight savings +time, and the ambiguous period after as standard time. + + +>>> warsaw = pytz.timezone('Europe/Warsaw') +>>> amb_dt1 = warsaw.localize(datetime(1915, 8, 4, 23, 59, 59), is_dst=True) +>>> amb_dt1.strftime(fmt) +'1915-08-04 23:59:59 WMT+0124' +>>> amb_dt2 = warsaw.localize(datetime(1915, 8, 4, 23, 59, 59), is_dst=False) +>>> amb_dt2.strftime(fmt) +'1915-08-04 23:59:59 CET+0100' +>>> switch_dt = warsaw.localize(datetime(1915, 8, 5, 00, 00, 00), is_dst=False) +>>> switch_dt.strftime(fmt) +'1915-08-05 00:00:00 CET+0100' +>>> str(switch_dt - amb_dt1) +'0:24:01' +>>> str(switch_dt - amb_dt2) +'0:00:01' + +The best way of creating a time during an ambiguous time period is +by converting from another timezone such as UTC: + +>>> utc_dt = datetime(1915, 8, 4, 22, 36, tzinfo=pytz.utc) +>>> utc_dt.astimezone(warsaw).strftime(fmt) +'1915-08-04 23:36:00 CET+0100' + +The standard Python way of handling all these ambiguities is not to +handle them, such as demonstrated in this example using the US/Eastern +timezone definition from the Python documentation (Note that this +implementation only works for dates between 1987 and 2006 - it is +included for tests only!): + +>>> from pytz.reference import Eastern # pytz.reference only for tests +>>> dt = datetime(2002, 10, 27, 0, 30, tzinfo=Eastern) +>>> str(dt) +'2002-10-27 00:30:00-04:00' +>>> str(dt + timedelta(hours=1)) +'2002-10-27 01:30:00-05:00' +>>> str(dt + timedelta(hours=2)) +'2002-10-27 02:30:00-05:00' +>>> str(dt + timedelta(hours=3)) +'2002-10-27 03:30:00-05:00' + +Notice the first two results? At first glance you might think they are +correct, but taking the UTC offset into account you find that they are +actually two hours appart instead of the 1 hour we asked for. + +>>> from pytz.reference import UTC # pytz.reference only for tests +>>> str(dt.astimezone(UTC)) +'2002-10-27 04:30:00+00:00' +>>> str((dt + timedelta(hours=1)).astimezone(UTC)) +'2002-10-27 06:30:00+00:00' + + +Country Information +~~~~~~~~~~~~~~~~~~~ + +A mechanism is provided to access the timezones commonly in use +for a particular country, looked up using the ISO 3166 country code. +It returns a list of strings that can be used to retrieve the relevant +tzinfo instance using ``pytz.timezone()``: + +>>> print(' '.join(pytz.country_timezones['nz'])) +Pacific/Auckland Pacific/Chatham + +The Olson database comes with a ISO 3166 country code to English country +name mapping that pytz exposes as a dictionary: + +>>> print(pytz.country_names['nz']) +New Zealand + + +What is UTC +~~~~~~~~~~~ + +'UTC' is `Coordinated Universal Time`_. It is a successor to, but distinct +from, Greenwich Mean Time (GMT) and the various definitions of Universal +Time. UTC is now the worldwide standard for regulating clocks and time +measurement. + +All other timezones are defined relative to UTC, and include offsets like +UTC+0800 - hours to add or subtract from UTC to derive the local time. No +daylight saving time occurs in UTC, making it a useful timezone to perform +date arithmetic without worrying about the confusion and ambiguities caused +by daylight saving time transitions, your country changing its timezone, or +mobile computers that roam through multiple timezones. + +.. _Coordinated Universal Time: https://en.wikipedia.org/wiki/Coordinated_Universal_Time + + +Helpers +~~~~~~~ + +There are two lists of timezones provided. + +``all_timezones`` is the exhaustive list of the timezone names that can +be used. + +>>> from pytz import all_timezones +>>> len(all_timezones) >= 500 +True +>>> 'Etc/Greenwich' in all_timezones +True + +``common_timezones`` is a list of useful, current timezones. It doesn't +contain deprecated zones or historical zones, except for a few I've +deemed in common usage, such as US/Eastern (open a bug report if you +think other timezones are deserving of being included here). It is also +a sequence of strings. + +>>> from pytz import common_timezones +>>> len(common_timezones) < len(all_timezones) +True +>>> 'Etc/Greenwich' in common_timezones +False +>>> 'Australia/Melbourne' in common_timezones +True +>>> 'US/Eastern' in common_timezones +True +>>> 'Canada/Eastern' in common_timezones +True +>>> 'Australia/Yancowinna' in all_timezones +True +>>> 'Australia/Yancowinna' in common_timezones +False + +Both ``common_timezones`` and ``all_timezones`` are alphabetically +sorted: + +>>> common_timezones_dupe = common_timezones[:] +>>> common_timezones_dupe.sort() +>>> common_timezones == common_timezones_dupe +True +>>> all_timezones_dupe = all_timezones[:] +>>> all_timezones_dupe.sort() +>>> all_timezones == all_timezones_dupe +True + +``all_timezones`` and ``common_timezones`` are also available as sets. + +>>> from pytz import all_timezones_set, common_timezones_set +>>> 'US/Eastern' in all_timezones_set +True +>>> 'US/Eastern' in common_timezones_set +True +>>> 'Australia/Victoria' in common_timezones_set +False + +You can also retrieve lists of timezones used by particular countries +using the ``country_timezones()`` function. It requires an ISO-3166 +two letter country code. + +>>> from pytz import country_timezones +>>> print(' '.join(country_timezones('ch'))) +Europe/Zurich +>>> print(' '.join(country_timezones('CH'))) +Europe/Zurich + + +Internationalization - i18n/l10n +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Pytz is an interface to the IANA database, which uses ASCII names. The `Unicode Consortium's Unicode Locales (CLDR) <http://cldr.unicode.org>`_ +project provides translations. Thomas Khyn's +`l18n <https://pypi.org/project/l18n/>`_ package can be used to access +these translations from Python. + + +License +~~~~~~~ + +MIT license. + +This code is also available as part of Zope 3 under the Zope Public +License, Version 2.1 (ZPL). + +I'm happy to relicense this code if necessary for inclusion in other +open source projects. + + +Latest Versions +~~~~~~~~~~~~~~~ + +This package will be updated after releases of the Olson timezone +database. The latest version can be downloaded from the `Python Package +Index <https://pypi.org/project/pytz/>`_. The code that is used +to generate this distribution is hosted on launchpad.net and available +using git:: + + git clone https://git.launchpad.net/pytz + +A mirror on github is also available at https://github.com/stub42/pytz + +Announcements of new releases are made on +`Launchpad <https://launchpad.net/pytz>`_, and the +`Atom feed <http://feeds.launchpad.net/pytz/announcements.atom>`_ +hosted there. + + +Bugs, Feature Requests & Patches +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Bugs can be reported using `Launchpad <https://bugs.launchpad.net/pytz>`__. + + +Issues & Limitations +~~~~~~~~~~~~~~~~~~~~ + +- Offsets from UTC are rounded to the nearest whole minute, so timezones + such as Europe/Amsterdam pre 1937 will be up to 30 seconds out. This + is a limitation of the Python datetime library. + +- If you think a timezone definition is incorrect, I probably can't fix + it. pytz is a direct translation of the Olson timezone database, and + changes to the timezone definitions need to be made to this source. + If you find errors they should be reported to the time zone mailing + list, linked from http://www.iana.org/time-zones. + + +Further Reading +~~~~~~~~~~~~~~~ + +More info than you want to know about timezones: +http://www.twinsun.com/tz/tz-link.htm + + +Contact +~~~~~~~ + +Stuart Bishop <stuart@stuartbishop.net> + + diff --git a/src/pytz/__init__.py b/src/pytz/__init__.py new file mode 100644 index 0000000..5c05066 --- /dev/null +++ b/src/pytz/__init__.py @@ -0,0 +1,503 @@ +''' +datetime.tzinfo timezone definitions generated from the +Olson timezone database: + + ftp://elsie.nci.nih.gov/pub/tz*.tar.gz + +See the datetime section of the Python Library Reference for information +on how to use these modules. +''' + +import sys +import datetime +import os.path + +from pytz.exceptions import AmbiguousTimeError +from pytz.exceptions import InvalidTimeError +from pytz.exceptions import NonExistentTimeError +from pytz.exceptions import UnknownTimeZoneError +from pytz.lazy import LazyDict, LazyList, LazySet # noqa +from pytz.tzinfo import unpickler, BaseTzInfo +from pytz.tzfile import build_tzinfo + + +# The IANA (nee Olson) database is updated several times a year. +OLSON_VERSION = '2019a' +VERSION = '2019.1' # pip compatible version number. +__version__ = VERSION + +OLSEN_VERSION = OLSON_VERSION # Old releases had this misspelling + +__all__ = [ + 'timezone', 'utc', 'country_timezones', 'country_names', + 'AmbiguousTimeError', 'InvalidTimeError', + 'NonExistentTimeError', 'UnknownTimeZoneError', + 'all_timezones', 'all_timezones_set', + 'common_timezones', 'common_timezones_set', + 'BaseTzInfo', +] + + +if sys.version_info[0] > 2: # Python 3.x + + # Python 3.x doesn't have unicode(), making writing code + # for Python 2.3 and Python 3.x a pain. + unicode = str + + def ascii(s): + r""" + >>> ascii('Hello') + 'Hello' + >>> ascii('\N{TRADE MARK SIGN}') #doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + UnicodeEncodeError: ... + """ + if type(s) == bytes: + s = s.decode('ASCII') + else: + s.encode('ASCII') # Raise an exception if not ASCII + return s # But the string - not a byte string. + +else: # Python 2.x + + def ascii(s): + r""" + >>> ascii('Hello') + 'Hello' + >>> ascii(u'Hello') + 'Hello' + >>> ascii(u'\N{TRADE MARK SIGN}') #doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + UnicodeEncodeError: ... + """ + return s.encode('ASCII') + + +def open_resource(name): + """Open a resource from the zoneinfo subdir for reading. + + Uses the pkg_resources module if available and no standard file + found at the calculated location. + + It is possible to specify different location for zoneinfo + subdir by using the PYTZ_TZDATADIR environment variable. + """ + name_parts = name.lstrip('/').split('/') + for part in name_parts: + if part == os.path.pardir or os.path.sep in part: + raise ValueError('Bad path segment: %r' % part) + zoneinfo_dir = os.environ.get('PYTZ_TZDATADIR', None) + if zoneinfo_dir is not None: + filename = os.path.join(zoneinfo_dir, *name_parts) + else: + filename = os.path.join(os.path.dirname(__file__), + 'zoneinfo', *name_parts) + if not os.path.exists(filename): + # http://bugs.launchpad.net/bugs/383171 - we avoid using this + # unless absolutely necessary to help when a broken version of + # pkg_resources is installed. + try: + from pkg_resources import resource_stream + except ImportError: + resource_stream = None + + if resource_stream is not None: + return resource_stream(__name__, 'zoneinfo/' + name) + return open(filename, 'rb') + + +def resource_exists(name): + """Return true if the given resource exists""" + try: + open_resource(name).close() + return True + except IOError: + return False + + +_tzinfo_cache = {} + + +def timezone(zone): + r''' Return a datetime.tzinfo implementation for the given timezone + + >>> from datetime import datetime, timedelta + >>> utc = timezone('UTC') + >>> eastern = timezone('US/Eastern') + >>> eastern.zone + 'US/Eastern' + >>> timezone(unicode('US/Eastern')) is eastern + True + >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc) + >>> loc_dt = utc_dt.astimezone(eastern) + >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' + >>> loc_dt.strftime(fmt) + '2002-10-27 01:00:00 EST (-0500)' + >>> (loc_dt - timedelta(minutes=10)).strftime(fmt) + '2002-10-27 00:50:00 EST (-0500)' + >>> eastern.normalize(loc_dt - timedelta(minutes=10)).strftime(fmt) + '2002-10-27 01:50:00 EDT (-0400)' + >>> (loc_dt + timedelta(minutes=10)).strftime(fmt) + '2002-10-27 01:10:00 EST (-0500)' + + Raises UnknownTimeZoneError if passed an unknown zone. + + >>> try: + ... timezone('Asia/Shangri-La') + ... except UnknownTimeZoneError: + ... print('Unknown') + Unknown + + >>> try: + ... timezone(unicode('\N{TRADE MARK SIGN}')) + ... except UnknownTimeZoneError: + ... print('Unknown') + Unknown + + ''' + if zone is None: + raise UnknownTimeZoneError(None) + + if zone.upper() == 'UTC': + return utc + + try: + zone = ascii(zone) + except UnicodeEncodeError: + # All valid timezones are ASCII + raise UnknownTimeZoneError(zone) + + zone = _case_insensitive_zone_lookup(_unmunge_zone(zone)) + if zone not in _tzinfo_cache: + if zone in all_timezones_set: # noqa + fp = open_resource(zone) + try: + _tzinfo_cache[zone] = build_tzinfo(zone, fp) + finally: + fp.close() + else: + raise UnknownTimeZoneError(zone) + + return _tzinfo_cache[zone] + + +def _unmunge_zone(zone): + """Undo the time zone name munging done by older versions of pytz.""" + return zone.replace('_plus_', '+').replace('_minus_', '-') + + +def _case_insensitive_zone_lookup(zone): + """case-insensitively matching timezone, else return zone unchanged""" + return _all_timezones_lower_to_standard.get(zone.lower()) or zone # noqa + + +ZERO = datetime.timedelta(0) +HOUR = datetime.timedelta(hours=1) + + +class UTC(BaseTzInfo): + """UTC + + Optimized UTC implementation. It unpickles using the single module global + instance defined beneath this class declaration. + """ + zone = "UTC" + + _utcoffset = ZERO + _dst = ZERO + _tzname = zone + + def fromutc(self, dt): + if dt.tzinfo is None: + return self.localize(dt) + return super(utc.__class__, self).fromutc(dt) + + def utcoffset(self, dt): + return ZERO + + def tzname(self, dt): + return "UTC" + + def dst(self, dt): + return ZERO + + def __reduce__(self): + return _UTC, () + + def localize(self, dt, is_dst=False): + '''Convert naive time to local time''' + if dt.tzinfo is not None: + raise ValueError('Not naive datetime (tzinfo is already set)') + return dt.replace(tzinfo=self) + + def normalize(self, dt, is_dst=False): + '''Correct the timezone information on the given datetime''' + if dt.tzinfo is self: + return dt + if dt.tzinfo is None: + raise ValueError('Naive time - no tzinfo set') + return dt.astimezone(self) + + def __repr__(self): + return "<UTC>" + + def __str__(self): + return "UTC" + + +UTC = utc = UTC() # UTC is a singleton + + +def _UTC(): + """Factory function for utc unpickling. + + Makes sure that unpickling a utc instance always returns the same + module global. + + These examples belong in the UTC class above, but it is obscured; or in + the README.txt, but we are not depending on Python 2.4 so integrating + the README.txt examples with the unit tests is not trivial. + + >>> import datetime, pickle + >>> dt = datetime.datetime(2005, 3, 1, 14, 13, 21, tzinfo=utc) + >>> naive = dt.replace(tzinfo=None) + >>> p = pickle.dumps(dt, 1) + >>> naive_p = pickle.dumps(naive, 1) + >>> len(p) - len(naive_p) + 17 + >>> new = pickle.loads(p) + >>> new == dt + True + >>> new is dt + False + >>> new.tzinfo is dt.tzinfo + True + >>> utc is UTC is timezone('UTC') + True + >>> utc is timezone('GMT') + False + """ + return utc + + +_UTC.__safe_for_unpickling__ = True + + +def _p(*args): + """Factory function for unpickling pytz tzinfo instances. + + Just a wrapper around tzinfo.unpickler to save a few bytes in each pickle + by shortening the path. + """ + return unpickler(*args) + + +_p.__safe_for_unpickling__ = True + + +class _CountryTimezoneDict(LazyDict): + """Map ISO 3166 country code to a list of timezone names commonly used + in that country. + + iso3166_code is the two letter code used to identify the country. + + >>> def print_list(list_of_strings): + ... 'We use a helper so doctests work under Python 2.3 -> 3.x' + ... for s in list_of_strings: + ... print(s) + + >>> print_list(country_timezones['nz']) + Pacific/Auckland + Pacific/Chatham + >>> print_list(country_timezones['ch']) + Europe/Zurich + >>> print_list(country_timezones['CH']) + Europe/Zurich + >>> print_list(country_timezones[unicode('ch')]) + Europe/Zurich + >>> print_list(country_timezones['XXX']) + Traceback (most recent call last): + ... + KeyError: 'XXX' + + Previously, this information was exposed as a function rather than a + dictionary. This is still supported:: + + >>> print_list(country_timezones('nz')) + Pacific/Auckland + Pacific/Chatham + """ + def __call__(self, iso3166_code): + """Backwards compatibility.""" + return self[iso3166_code] + + def _fill(self): + data = {} + zone_tab = open_resource('zone.tab') + try: + for line in zone_tab: + line = line.decode('UTF-8') + if line.startswith('#'): + continue + code, coordinates, zone = line.split(None, 4)[:3] + if zone not in all_timezones_set: # noqa + continue + try: + data[code].append(zone) + except KeyError: + data[code] = [zone] + self.data = data + finally: + zone_tab.close() + + +country_timezones = _CountryTimezoneDict() + + +class _CountryNameDict(LazyDict): + '''Dictionary proving ISO3166 code -> English name. + + >>> print(country_names['au']) + Australia + ''' + def _fill(self): + data = {} + zone_tab = open_resource('iso3166.tab') + try: + for line in zone_tab.readlines(): + line = line.decode('UTF-8') + if line.startswith('#'): + continue + code, name = line.split(None, 1) + data[code] = name.strip() + self.data = data + finally: + zone_tab.close() + + +country_names = _CountryNameDict() + + +# Time-zone info based solely on fixed offsets + +class _FixedOffset(datetime.tzinfo): + + zone = None # to match the standard pytz API + + def __init__(self, minutes): + if abs(minutes) >= 1440: + raise ValueError("absolute offset is too large", minutes) + self._minutes = minutes + self._offset = datetime.timedelta(minutes=minutes) + + def utcoffset(self, dt): + return self._offset + + def __reduce__(self): + return FixedOffset, (self._minutes, ) + + def dst(self, dt): + return ZERO + + def tzname(self, dt): + return None + + def __repr__(self): + return 'pytz.FixedOffset(%d)' % self._minutes + + def localize(self, dt, is_dst=False): + '''Convert naive time to local time''' + if dt.tzinfo is not None: + raise ValueError('Not naive datetime (tzinfo is already set)') + return dt.replace(tzinfo=self) + + def normalize(self, dt, is_dst=False): + '''Correct the timezone information on the given datetime''' + if dt.tzinfo is self: + return dt + if dt.tzinfo is None: + raise ValueError('Naive time - no tzinfo set') + return dt.astimezone(self) + + +def FixedOffset(offset, _tzinfos={}): + """return a fixed-offset timezone based off a number of minutes. + + >>> one = FixedOffset(-330) + >>> one + pytz.FixedOffset(-330) + >>> str(one.utcoffset(datetime.datetime.now())) + '-1 day, 18:30:00' + >>> str(one.dst(datetime.datetime.now())) + '0:00:00' + + >>> two = FixedOffset(1380) + >>> two + pytz.FixedOffset(1380) + >>> str(two.utcoffset(datetime.datetime.now())) + '23:00:00' + >>> str(two.dst(datetime.datetime.now())) + '0:00:00' + + The datetime.timedelta must be between the range of -1 and 1 day, + non-inclusive. + + >>> FixedOffset(1440) + Traceback (most recent call last): + ... + ValueError: ('absolute offset is too large', 1440) + + >>> FixedOffset(-1440) + Traceback (most recent call last): + ... + ValueError: ('absolute offset is too large', -1440) + + An offset of 0 is special-cased to return UTC. + + >>> FixedOffset(0) is UTC + True + + There should always be only one instance of a FixedOffset per timedelta. + This should be true for multiple creation calls. + + >>> FixedOffset(-330) is one + True + >>> FixedOffset(1380) is two + True + + It should also be true for pickling. + + >>> import pickle + >>> pickle.loads(pickle.dumps(one)) is one + True + >>> pickle.loads(pickle.dumps(two)) is two + True + """ + if offset == 0: + return UTC + + info = _tzinfos.get(offset) + if info is None: + # We haven't seen this one before. we need to save it. + + # Use setdefault to avoid a race condition and make sure we have + # only one + info = _tzinfos.setdefault(offset, _FixedOffset(offset)) + + return info + + +FixedOffset.__safe_for_unpickling__ = True + + +def _test(): + import doctest + sys.path.insert(0, os.pardir) + import pytz + return doctest.testmod(pytz) + + +if __name__ == '__main__': + _test() diff --git a/src/pytz/exceptions.py b/src/pytz/exceptions.py new file mode 100644 index 0000000..18df33e --- /dev/null +++ b/src/pytz/exceptions.py @@ -0,0 +1,48 @@ +''' +Custom exceptions raised by pytz. +''' + +__all__ = [ + 'UnknownTimeZoneError', 'InvalidTimeError', 'AmbiguousTimeError', + 'NonExistentTimeError', +] + + +class UnknownTimeZoneError(KeyError): + '''Exception raised when pytz is passed an unknown timezone. + + >>> isinstance(UnknownTimeZoneError(), LookupError) + True + + This class is actually a subclass of KeyError to provide backwards + compatibility with code relying on the undocumented behavior of earlier + pytz releases. + + >>> isinstance(UnknownTimeZoneError(), KeyError) + True + ''' + pass + + +class InvalidTimeError(Exception): + '''Base class for invalid time exceptions.''' + + +class AmbiguousTimeError(InvalidTimeError): + '''Exception raised when attempting to create an ambiguous wallclock time. + + At the end of a DST transition period, a particular wallclock time will + occur twice (once before the clocks are set back, once after). Both + possibilities may be correct, unless further information is supplied. + + See DstTzInfo.normalize() for more info + ''' + + +class NonExistentTimeError(InvalidTimeError): + '''Exception raised when attempting to create a wallclock time that + cannot exist. + + At the start of a DST transition period, the wallclock time jumps forward. + The instants jumped over never occur. + ''' diff --git a/src/pytz/lazy.py b/src/pytz/lazy.py new file mode 100644 index 0000000..39344fc --- /dev/null +++ b/src/pytz/lazy.py @@ -0,0 +1,172 @@ +from threading import RLock +try: + from collections.abc import Mapping as DictMixin +except ImportError: # Python < 3.3 + try: + from UserDict import DictMixin # Python 2 + except ImportError: # Python 3.0-3.3 + from collections import Mapping as DictMixin + + +# With lazy loading, we might end up with multiple threads triggering +# it at the same time. We need a lock. +_fill_lock = RLock() + + +class LazyDict(DictMixin): + """Dictionary populated on first use.""" + data = None + + def __getitem__(self, key): + if self.data is None: + _fill_lock.acquire() + try: + if self.data is None: + self._fill() + finally: + _fill_lock.release() + return self.data[key.upper()] + + def __contains__(self, key): + if self.data is None: + _fill_lock.acquire() + try: + if self.data is None: + self._fill() + finally: + _fill_lock.release() + return key in self.data + + def __iter__(self): + if self.data is None: + _fill_lock.acquire() + try: + if self.data is None: + self._fill() + finally: + _fill_lock.release() + return iter(self.data) + + def __len__(self): + if self.data is None: + _fill_lock.acquire() + try: + if self.data is None: + self._fill() + finally: + _fill_lock.release() + return len(self.data) + + def keys(self): + if self.data is None: + _fill_lock.acquire() + try: + if self.data is None: + self._fill() + finally: + _fill_lock.release() + return self.data.keys() + + +class LazyList(list): + """List populated on first use.""" + + _props = [ + '__str__', '__repr__', '__unicode__', + '__hash__', '__sizeof__', '__cmp__', + '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', + 'append', 'count', 'index', 'extend', 'insert', 'pop', 'remove', + 'reverse', 'sort', '__add__', '__radd__', '__iadd__', '__mul__', + '__rmul__', '__imul__', '__contains__', '__len__', '__nonzero__', + '__getitem__', '__setitem__', '__delitem__', '__iter__', + '__reversed__', '__getslice__', '__setslice__', '__delslice__'] + + def __new__(cls, fill_iter=None): + + if fill_iter is None: + return list() + + # We need a new class as we will be dynamically messing with its + # methods. + class LazyList(list): + pass + + fill_iter = [fill_iter] + + def lazy(name): + def _lazy(self, *args, **kw): + _fill_lock.acquire() + try: + if len(fill_iter) > 0: + list.extend(self, fill_iter.pop()) + for method_name in cls._props: + delattr(LazyList, method_name) + finally: + _fill_lock.release() + return getattr(list, name)(self, *args, **kw) + return _lazy + + for name in cls._props: + setattr(LazyList, name, lazy(name)) + + new_list = LazyList() + return new_list + +# Not all versions of Python declare the same magic methods. +# Filter out properties that don't exist in this version of Python +# from the list. +LazyList._props = [prop for prop in LazyList._props if hasattr(list, prop)] + + +class LazySet(set): + """Set populated on first use.""" + + _props = ( + '__str__', '__repr__', '__unicode__', + '__hash__', '__sizeof__', '__cmp__', + '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', + '__contains__', '__len__', '__nonzero__', + '__getitem__', '__setitem__', '__delitem__', '__iter__', + '__sub__', '__and__', '__xor__', '__or__', + '__rsub__', '__rand__', '__rxor__', '__ror__', + '__isub__', '__iand__', '__ixor__', '__ior__', + 'add', 'clear', 'copy', 'difference', 'difference_update', + 'discard', 'intersection', 'intersection_update', 'isdisjoint', + 'issubset', 'issuperset', 'pop', 'remove', + 'symmetric_difference', 'symmetric_difference_update', + 'union', 'update') + + def __new__(cls, fill_iter=None): + + if fill_iter is None: + return set() + + class LazySet(set): + pass + + fill_iter = [fill_iter] + + def lazy(name): + def _lazy(self, *args, **kw): + _fill_lock.acquire() + try: + if len(fill_iter) > 0: + for i in fill_iter.pop(): + set.add(self, i) + for method_name in cls._props: + delattr(LazySet, method_name) + finally: + _fill_lock.release() + return getattr(set, name)(self, *args, **kw) + return _lazy + + for name in cls._props: + setattr(LazySet, name, lazy(name)) + + new_set = LazySet() + return new_set + +# Not all versions of Python declare the same magic methods. +# Filter out properties that don't exist in this version of Python +# from the list. +LazySet._props = [prop for prop in LazySet._props if hasattr(set, prop)] diff --git a/src/pytz/locales/pytz.pot b/src/pytz/locales/pytz.pot new file mode 100644 index 0000000..9c76c11 --- /dev/null +++ b/src/pytz/locales/pytz.pot @@ -0,0 +1,2 @@ +# This file will be generated by gen_pot.py. This is just a stub to keep +# things working when this generated file is not available. diff --git a/src/pytz/reference.py b/src/pytz/reference.py new file mode 100644 index 0000000..f765ca0 --- /dev/null +++ b/src/pytz/reference.py @@ -0,0 +1,140 @@ +''' +Reference tzinfo implementations from the Python docs. +Used for testing against as they are only correct for the years +1987 to 2006. Do not use these for real code. +''' + +from datetime import tzinfo, timedelta, datetime +from pytz import HOUR, ZERO, UTC + +__all__ = [ + 'FixedOffset', + 'LocalTimezone', + 'USTimeZone', + 'Eastern', + 'Central', + 'Mountain', + 'Pacific', + 'UTC' +] + + +# A class building tzinfo objects for fixed-offset time zones. +# Note that FixedOffset(0, "UTC") is a different way to build a +# UTC tzinfo object. +class FixedOffset(tzinfo): + """Fixed offset in minutes east from UTC.""" + + def __init__(self, offset, name): + self.__offset = timedelta(minutes=offset) + self.__name = name + + def utcoffset(self, dt): + return self.__offset + + def tzname(self, dt): + return self.__name + + def dst(self, dt): + return ZERO + + +import time as _time + +STDOFFSET = timedelta(seconds=-_time.timezone) +if _time.daylight: + DSTOFFSET = timedelta(seconds=-_time.altzone) +else: + DSTOFFSET = STDOFFSET + +DSTDIFF = DSTOFFSET - STDOFFSET + + +# A class capturing the platform's idea of local time. +class LocalTimezone(tzinfo): + + def utcoffset(self, dt): + if self._isdst(dt): + return DSTOFFSET + else: + return STDOFFSET + + def dst(self, dt): + if self._isdst(dt): + return DSTDIFF + else: + return ZERO + + def tzname(self, dt): + return _time.tzname[self._isdst(dt)] + + def _isdst(self, dt): + tt = (dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second, + dt.weekday(), 0, -1) + stamp = _time.mktime(tt) + tt = _time.localtime(stamp) + return tt.tm_isdst > 0 + +Local = LocalTimezone() + + +def first_sunday_on_or_after(dt): + days_to_go = 6 - dt.weekday() + if days_to_go: + dt += timedelta(days_to_go) + return dt + + +# In the US, DST starts at 2am (standard time) on the first Sunday in April. +DSTSTART = datetime(1, 4, 1, 2) +# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct. +# which is the first Sunday on or after Oct 25. +DSTEND = datetime(1, 10, 25, 1) + + +# A complete implementation of current DST rules for major US time zones. +class USTimeZone(tzinfo): + + def __init__(self, hours, reprname, stdname, dstname): + self.stdoffset = timedelta(hours=hours) + self.reprname = reprname + self.stdname = stdname + self.dstname = dstname + + def __repr__(self): + return self.reprname + + def tzname(self, dt): + if self.dst(dt): + return self.dstname + else: + return self.stdname + + def utcoffset(self, dt): + return self.stdoffset + self.dst(dt) + + def dst(self, dt): + if dt is None or dt.tzinfo is None: + # An exception may be sensible here, in one or both cases. + # It depends on how you want to treat them. The default + # fromutc() implementation (called by the default astimezone() + # implementation) passes a datetime with dt.tzinfo is self. + return ZERO + assert dt.tzinfo is self + + # Find first Sunday in April & the last in October. + start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year)) + end = first_sunday_on_or_after(DSTEND.replace(year=dt.year)) + + # Can't compare naive to aware objects, so strip the timezone from + # dt first. + if start <= dt.replace(tzinfo=None) < end: + return HOUR + else: + return ZERO + +Eastern = USTimeZone(-5, "Eastern", "EST", "EDT") +Central = USTimeZone(-6, "Central", "CST", "CDT") +Mountain = USTimeZone(-7, "Mountain", "MST", "MDT") +Pacific = USTimeZone(-8, "Pacific", "PST", "PDT") diff --git a/src/pytz/tests/test_docs.py b/src/pytz/tests/test_docs.py new file mode 100644 index 0000000..c4ed4a3 --- /dev/null +++ b/src/pytz/tests/test_docs.py @@ -0,0 +1,34 @@ +# -*- coding: ascii -*- + +from doctest import DocFileSuite +import unittest +import os.path +import sys + +THIS_DIR = os.path.dirname(__file__) + +README = os.path.join(THIS_DIR, os.pardir, os.pardir, 'README.txt') + + +class DocumentationTestCase(unittest.TestCase): + def test_readme_encoding(self): + '''Confirm the README.txt is pure ASCII.''' + f = open(README, 'rb') + try: + f.read().decode('ASCII') + finally: + f.close() + + +def test_suite(): + "For the Z3 test runner" + return unittest.TestSuite(( + DocumentationTestCase('test_readme_encoding'), + DocFileSuite(os.path.join(os.pardir, os.pardir, 'README.txt')))) + + +if __name__ == '__main__': + sys.path.insert( + 0, os.path.abspath(os.path.join(THIS_DIR, os.pardir, os.pardir)) + ) + unittest.main(defaultTest='test_suite') diff --git a/src/pytz/tests/test_lazy.py b/src/pytz/tests/test_lazy.py new file mode 100644 index 0000000..3709703 --- /dev/null +++ b/src/pytz/tests/test_lazy.py @@ -0,0 +1,315 @@ +from operator import ( + eq, ge, gt, le, lt, ne, add, concat, not_, sub, and_, or_, xor +) +import os.path +import sys +import unittest +import warnings + + +if __name__ == '__main__': + # Only munge path if invoked as a script. Testrunners should have setup + # the paths already + sys.path.insert(0, os.path.abspath(os.path.join(os.pardir, os.pardir))) + + +from pytz.lazy import LazyList, LazySet + + +class LazyListTestCase(unittest.TestCase): + initial_data = [3, 2, 1] + + def setUp(self): + self.base = [3, 2, 1] + self.lesser = [2, 1, 0] + self.greater = [4, 3, 2] + + self.lazy = LazyList(iter(list(self.base))) + + def test_unary_ops(self): + unary_ops = [str, repr, len, bool, not_] + try: + unary_ops.append(unicode) + except NameError: + pass # unicode no longer exists in Python 3. + + for op in unary_ops: + self.assertEqual( + op(self.lazy), + op(self.base), str(op)) + + def test_binary_ops(self): + binary_ops = [eq, ge, gt, le, lt, ne, add, concat] + try: + binary_ops.append(cmp) + except NameError: + pass # cmp no longer exists in Python 3. + + for op in binary_ops: + self.assertEqual( + op(self.lazy, self.lazy), + op(self.base, self.base), str(op)) + for other in [self.base, self.lesser, self.greater]: + self.assertEqual( + op(self.lazy, other), + op(self.base, other), '%s %s' % (op, other)) + self.assertEqual( + op(other, self.lazy), + op(other, self.base), '%s %s' % (op, other)) + + # Multiplication + self.assertEqual(self.lazy * 3, self.base * 3) + self.assertEqual(3 * self.lazy, 3 * self.base) + + # Contains + self.assertTrue(2 in self.lazy) + self.assertFalse(42 in self.lazy) + + def test_iadd(self): + self.lazy += [1] + self.base += [1] + self.assertEqual(self.lazy, self.base) + + def test_bool(self): + self.assertTrue(bool(self.lazy)) + self.assertFalse(bool(LazyList())) + self.assertFalse(bool(LazyList(iter([])))) + + def test_hash(self): + self.assertRaises(TypeError, hash, self.lazy) + + def test_isinstance(self): + self.assertTrue(isinstance(self.lazy, list)) + self.assertFalse(isinstance(self.lazy, tuple)) + + def test_callable(self): + try: + callable + except NameError: + return # No longer exists with Python 3. + self.assertFalse(callable(self.lazy)) + + def test_append(self): + self.base.append('extra') + self.lazy.append('extra') + self.assertEqual(self.lazy, self.base) + + def test_count(self): + self.assertEqual(self.lazy.count(2), 1) + + def test_index(self): + self.assertEqual(self.lazy.index(2), 1) + + def test_extend(self): + self.base.extend([6, 7]) + self.lazy.extend([6, 7]) + self.assertEqual(self.lazy, self.base) + + def test_insert(self): + self.base.insert(0, 'ping') + self.lazy.insert(0, 'ping') + self.assertEqual(self.lazy, self.base) + + def test_pop(self): + self.assertEqual(self.lazy.pop(), self.base.pop()) + self.assertEqual(self.lazy, self.base) + + def test_remove(self): + self.base.remove(2) + self.lazy.remove(2) + self.assertEqual(self.lazy, self.base) + + def test_reverse(self): + self.base.reverse() + self.lazy.reverse() + self.assertEqual(self.lazy, self.base) + + def test_reversed(self): + self.assertEqual(list(reversed(self.lazy)), list(reversed(self.base))) + + def test_sort(self): + self.base.sort() + self.assertNotEqual(self.lazy, self.base, 'Test data already sorted') + self.lazy.sort() + self.assertEqual(self.lazy, self.base) + + def test_sorted(self): + self.assertEqual(sorted(self.lazy), sorted(self.base)) + + def test_getitem(self): + for idx in range(-len(self.base), len(self.base)): + self.assertEqual(self.lazy[idx], self.base[idx]) + + def test_setitem(self): + for idx in range(-len(self.base), len(self.base)): + self.base[idx] = idx + 1000 + self.assertNotEqual(self.lazy, self.base) + self.lazy[idx] = idx + 1000 + self.assertEqual(self.lazy, self.base) + + def test_delitem(self): + del self.base[0] + self.assertNotEqual(self.lazy, self.base) + del self.lazy[0] + self.assertEqual(self.lazy, self.base) + + del self.base[-2] + self.assertNotEqual(self.lazy, self.base) + del self.lazy[-2] + self.assertEqual(self.lazy, self.base) + + def test_iter(self): + self.assertEqual(list(iter(self.lazy)), list(iter(self.base))) + + def test_getslice(self): + for i in range(-len(self.base), len(self.base)): + for j in range(-len(self.base), len(self.base)): + for step in [-1, 1]: + self.assertEqual(self.lazy[i:j:step], self.base[i:j:step]) + + def test_setslice(self): + for i in range(-len(self.base), len(self.base)): + for j in range(-len(self.base), len(self.base)): + for step in [-1, 1]: + replacement = range(0, len(self.base[i:j:step])) + self.base[i:j:step] = replacement + self.lazy[i:j:step] = replacement + self.assertEqual(self.lazy, self.base) + + def test_delslice(self): + del self.base[0:1] + del self.lazy[0:1] + self.assertEqual(self.lazy, self.base) + + del self.base[-1:1:-1] + del self.lazy[-1:1:-1] + self.assertEqual(self.lazy, self.base) + + +class LazySetTestCase(unittest.TestCase): + initial_data = set([3, 2, 1]) + + def setUp(self): + self.base = set([3, 2, 1]) + self.lazy = LazySet(iter(set(self.base))) + + def test_unary_ops(self): + # These ops just need to work. + unary_ops = [str, repr] + try: + unary_ops.append(unicode) + except NameError: + pass # unicode no longer exists in Python 3. + + for op in unary_ops: + op(self.lazy) # These ops just need to work. + + # These ops should return identical values as a real set. + unary_ops = [len, bool, not_] + + for op in unary_ops: + self.assertEqual( + op(self.lazy), + op(self.base), '%s(lazy) == %r' % (op, op(self.lazy))) + + def test_binary_ops(self): + binary_ops = [eq, ge, gt, le, lt, ne, sub, and_, or_, xor] + try: + binary_ops.append(cmp) + except NameError: + pass # cmp no longer exists in Python 3. + + for op in binary_ops: + self.assertEqual( + op(self.lazy, self.lazy), + op(self.base, self.base), str(op)) + self.assertEqual( + op(self.lazy, self.base), + op(self.base, self.base), str(op)) + self.assertEqual( + op(self.base, self.lazy), + op(self.base, self.base), str(op)) + + # Contains + self.assertTrue(2 in self.lazy) + self.assertFalse(42 in self.lazy) + + def test_iops(self): + try: + iops = [isub, iand, ior, ixor] + except NameError: + return # Don't exist in older Python versions. + for op in iops: + # Mutating operators, so make fresh copies. + lazy = LazySet(self.base) + base = self.base.copy() + op(lazy, set([1])) + op(base, set([1])) + self.assertEqual(lazy, base, str(op)) + + def test_bool(self): + self.assertTrue(bool(self.lazy)) + self.assertFalse(bool(LazySet())) + self.assertFalse(bool(LazySet(iter([])))) + + def test_hash(self): + self.assertRaises(TypeError, hash, self.lazy) + + def test_isinstance(self): + self.assertTrue(isinstance(self.lazy, set)) + + def test_callable(self): + try: + callable + except NameError: + return # No longer exists with Python 3. + self.assertFalse(callable(self.lazy)) + + def test_add(self): + self.base.add('extra') + self.lazy.add('extra') + self.assertEqual(self.lazy, self.base) + + def test_copy(self): + self.assertEqual(self.lazy.copy(), self.base) + + def test_method_ops(self): + ops = [ + 'difference', 'intersection', 'isdisjoint', + 'issubset', 'issuperset', 'symmetric_difference', 'union', + 'difference_update', 'intersection_update', + 'symmetric_difference_update', 'update'] + for op in ops: + if not hasattr(set, op): + continue # Not in this version of Python. + # Make a copy, as some of the ops are mutating. + lazy = LazySet(set(self.base)) + base = set(self.base) + self.assertEqual( + getattr(lazy, op)(set([1])), + getattr(base, op)(set([1])), op) + self.assertEqual(lazy, base, op) + + def test_discard(self): + self.base.discard(1) + self.assertNotEqual(self.lazy, self.base) + self.lazy.discard(1) + self.assertEqual(self.lazy, self.base) + + def test_pop(self): + self.assertEqual(self.lazy.pop(), self.base.pop()) + self.assertEqual(self.lazy, self.base) + + def test_remove(self): + self.base.remove(2) + self.lazy.remove(2) + self.assertEqual(self.lazy, self.base) + + def test_clear(self): + self.lazy.clear() + self.assertEqual(self.lazy, set()) + + +if __name__ == '__main__': + warnings.simplefilter("error") # Warnings should be fatal in tests. + unittest.main() diff --git a/src/pytz/tests/test_tzinfo.py b/src/pytz/tests/test_tzinfo.py new file mode 100644 index 0000000..e56fef5 --- /dev/null +++ b/src/pytz/tests/test_tzinfo.py @@ -0,0 +1,863 @@ +# -*- coding: ascii -*- + +import doctest +import sys +import os +import os.path +import unittest +try: + import cPickle as pickle +except ImportError: + import pickle +from datetime import ( + datetime, + timedelta +) +import warnings + +if __name__ == '__main__': + # Only munge path if invoked as a script. Testrunners should have setup + # the paths already + sys.path.insert(0, os.path.abspath(os.path.join(os.pardir, os.pardir))) + +import pytz # noqa +from pytz import reference # noqa +from pytz.tzfile import _byte_string # noqa +from pytz.tzinfo import DstTzInfo, StaticTzInfo # noqa + +# I test for expected version to ensure the correct version of pytz is +# actually being tested. +EXPECTED_VERSION = '2019.1' +EXPECTED_OLSON_VERSION = '2019a' + +fmt = '%Y-%m-%d %H:%M:%S %Z%z' + +NOTIME = timedelta(0) + +# GMT is a tzinfo.StaticTzInfo--the class we primarily want to test--while +# UTC is reference implementation. They both have the same timezone meaning. +UTC = pytz.timezone('UTC') +GMT = pytz.timezone('GMT') +assert isinstance(GMT, StaticTzInfo), 'GMT is no longer a StaticTzInfo' + + +def prettydt(dt): + """datetime as a string using a known format. + + We don't use strftime as it doesn't handle years earlier than 1900 + per http://bugs.python.org/issue1777412 + """ + if dt.utcoffset() >= timedelta(0): + offset = '+%s' % (dt.utcoffset(),) + else: + offset = '-%s' % (-1 * dt.utcoffset(),) + return '%04d-%02d-%02d %02d:%02d:%02d %s %s' % ( + dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second, + dt.tzname(), offset) + + +if sys.version_info[0] > 2: + # Python 3.x doesn't have unicode(), making writing code + # for Python 2.3 and Python 3.x a pain. + unicode = str + + +class BasicTest(unittest.TestCase): + + def testVersion(self): + # Ensuring the correct version of pytz has been loaded + self.assertEqual( + EXPECTED_VERSION, pytz.__version__, + 'Incorrect pytz version loaded. Import path is stuffed ' + 'or this test needs updating. (Wanted %s, got %s)' + % (EXPECTED_VERSION, pytz.__version__) + ) + + self.assertEqual( + EXPECTED_OLSON_VERSION, pytz.OLSON_VERSION, + 'Incorrect pytz version loaded. Import path is stuffed ' + 'or this test needs updating. (Wanted %s, got %s)' + % (EXPECTED_OLSON_VERSION, pytz.OLSON_VERSION) + ) + + def testGMT(self): + now = datetime.now(tz=GMT) + self.assertTrue(now.utcoffset() == NOTIME) + self.assertTrue(now.dst() == NOTIME) + self.assertTrue(now.timetuple() == now.utctimetuple()) + self.assertTrue(now == now.replace(tzinfo=UTC)) + + def testReferenceUTC(self): + now = datetime.now(tz=UTC) + self.assertTrue(now.utcoffset() == NOTIME) + self.assertTrue(now.dst() == NOTIME) + self.assertTrue(now.timetuple() == now.utctimetuple()) + + def testUnknownOffsets(self): + # This tzinfo behavior is required to make + # datetime.time.{utcoffset, dst, tzname} work as documented. + + dst_tz = pytz.timezone('US/Eastern') + + # This information is not known when we don't have a date, + # so return None per API. + self.assertTrue(dst_tz.utcoffset(None) is None) + self.assertTrue(dst_tz.dst(None) is None) + # We don't know the abbreviation, but this is still a valid + # tzname per the Python documentation. + self.assertEqual(dst_tz.tzname(None), 'US/Eastern') + + def clearCache(self): + pytz._tzinfo_cache.clear() + + def testUnicodeTimezone(self): + # We need to ensure that cold lookups work for both Unicode + # and traditional strings, and that the desired singleton is + # returned. + self.clearCache() + eastern = pytz.timezone(unicode('US/Eastern')) + self.assertTrue(eastern is pytz.timezone('US/Eastern')) + + self.clearCache() + eastern = pytz.timezone('US/Eastern') + self.assertTrue(eastern is pytz.timezone(unicode('US/Eastern'))) + + def testStaticTzInfo(self): + # Ensure that static timezones are correctly detected, + # per lp:1602807 + static = pytz.timezone('Etc/GMT-4') + self.assertTrue(isinstance(static, StaticTzInfo)) + + +class PicklingTest(unittest.TestCase): + + def _roundtrip_tzinfo(self, tz): + p = pickle.dumps(tz) + unpickled_tz = pickle.loads(p) + self.assertTrue(tz is unpickled_tz, '%s did not roundtrip' % tz.zone) + + def _roundtrip_datetime(self, dt): + # Ensure that the tzinfo attached to a datetime instance + # is identical to the one returned. This is important for + # DST timezones, as some state is stored in the tzinfo. + tz = dt.tzinfo + p = pickle.dumps(dt) + unpickled_dt = pickle.loads(p) + unpickled_tz = unpickled_dt.tzinfo + self.assertTrue(tz is unpickled_tz, '%s did not roundtrip' % tz.zone) + + def testDst(self): + tz = pytz.timezone('Europe/Amsterdam') + dt = datetime(2004, 2, 1, 0, 0, 0) + + for localized_tz in tz._tzinfos.values(): + self._roundtrip_tzinfo(localized_tz) + self._roundtrip_datetime(dt.replace(tzinfo=localized_tz)) + + def testRoundtrip(self): + for zone in pytz.all_timezones: + tz = pytz.timezone(zone) + self._roundtrip_tzinfo(tz) + + def testDatabaseFixes(self): + # Hack the pickle to make it refer to a timezone abbreviation + # that does not match anything. The unpickler should be able + # to repair this case + tz = pytz.timezone('Australia/Melbourne') + p = pickle.dumps(tz) + tzname = tz._tzname + hacked_p = p.replace( + _byte_string(tzname), + _byte_string('?' * len(tzname)) + ) + self.assertNotEqual(p, hacked_p) + unpickled_tz = pickle.loads(hacked_p) + self.assertTrue(tz is unpickled_tz) + + # Simulate a database correction. In this case, the incorrect + # data will continue to be used. + p = pickle.dumps(tz) + new_utcoffset = tz._utcoffset.seconds + 42 + + # Python 3 introduced a new pickle protocol where numbers are stored in + # hexadecimal representation. Here we extract the pickle + # representation of the number for the current Python version. + old_pickle_pattern = pickle.dumps(tz._utcoffset.seconds)[3:-1] + new_pickle_pattern = pickle.dumps(new_utcoffset)[3:-1] + hacked_p = p.replace(old_pickle_pattern, new_pickle_pattern) + + self.assertNotEqual(p, hacked_p) + unpickled_tz = pickle.loads(hacked_p) + self.assertEqual(unpickled_tz._utcoffset.seconds, new_utcoffset) + self.assertTrue(tz is not unpickled_tz) + + def testOldPickles(self): + # Ensure that applications serializing pytz instances as pickles + # have no troubles upgrading to a new pytz release. These pickles + # where created with pytz2006j + east1 = pickle.loads( + _byte_string( + "cpytz\n_p\np1\n(S'US/Eastern'\np2\nI-18000\n" + "I0\nS'EST'\np3\ntRp4\n." + ) + ) + east2 = pytz.timezone('US/Eastern').localize( + datetime(2006, 1, 1)).tzinfo + self.assertTrue(east1 is east2) + + # Confirm changes in name munging between 2006j and 2007c cause + # no problems. + pap1 = pickle.loads(_byte_string( + "cpytz\n_p\np1\n(S'America/Port_minus_au_minus_Prince'" + "\np2\nI-17340\nI0\nS'PPMT'\np3\ntRp4\n.")) + pap2 = pytz.timezone('America/Port-au-Prince').localize( + datetime(1910, 1, 1)).tzinfo + self.assertTrue(pap1 is pap2) + + gmt1 = pickle.loads(_byte_string( + "cpytz\n_p\np1\n(S'Etc/GMT_plus_10'\np2\ntRp3\n.")) + gmt2 = pytz.timezone('Etc/GMT+10') + self.assertTrue(gmt1 is gmt2) + + +class USEasternDSTStartTestCase(unittest.TestCase): + tzinfo = pytz.timezone('US/Eastern') + + # 24 hours before DST changeover + transition_time = datetime(2002, 4, 7, 7, 0, 0, tzinfo=UTC) + + # Increase for 'flexible' DST transitions due to 1 minute granularity + # of Python's datetime library + instant = timedelta(seconds=1) + + # before transition + before = { + 'tzname': 'EST', + 'utcoffset': timedelta(hours=-5), + 'dst': timedelta(hours=0), + } + + # after transition + after = { + 'tzname': 'EDT', + 'utcoffset': timedelta(hours=-4), + 'dst': timedelta(hours=1), + } + + def _test_tzname(self, utc_dt, wanted): + tzname = wanted['tzname'] + dt = utc_dt.astimezone(self.tzinfo) + self.assertEqual( + dt.tzname(), tzname, + 'Expected %s as tzname for %s. Got %s' % ( + tzname, str(utc_dt), dt.tzname() + ) + ) + + def _test_utcoffset(self, utc_dt, wanted): + utcoffset = wanted['utcoffset'] + dt = utc_dt.astimezone(self.tzinfo) + self.assertEqual( + dt.utcoffset(), wanted['utcoffset'], + 'Expected %s as utcoffset for %s. Got %s' % ( + utcoffset, utc_dt, dt.utcoffset() + ) + ) + + def _test_dst(self, utc_dt, wanted): + dst = wanted['dst'] + dt = utc_dt.astimezone(self.tzinfo) + self.assertEqual( + dt.dst(), dst, + 'Expected %s as dst for %s. Got %s' % (dst, utc_dt, dt.dst()) + ) + + def test_arithmetic(self): + utc_dt = self.transition_time + + for days in range(-420, 720, 20): + delta = timedelta(days=days) + + # Make sure we can get back where we started + dt = utc_dt.astimezone(self.tzinfo) + dt2 = dt + delta + dt2 = dt2 - delta + self.assertEqual(dt, dt2) + + # Make sure arithmetic crossing DST boundaries ends + # up in the correct timezone after normalization + utc_plus_delta = (utc_dt + delta).astimezone(self.tzinfo) + local_plus_delta = self.tzinfo.normalize(dt + delta) + self.assertEqual( + prettydt(utc_plus_delta), prettydt(local_plus_delta), + 'Incorrect result for delta==%d days. Wanted %r. Got %r' % ( + days, prettydt(utc_plus_delta), prettydt(local_plus_delta), + ) + ) + + def _test_all(self, utc_dt, wanted): + self._test_utcoffset(utc_dt, wanted) + self._test_tzname(utc_dt, wanted) + self._test_dst(utc_dt, wanted) + + def testDayBefore(self): + self._test_all( + self.transition_time - timedelta(days=1), self.before + ) + + def testTwoHoursBefore(self): + self._test_all( + self.transition_time - timedelta(hours=2), self.before + ) + + def testHourBefore(self): + self._test_all( + self.transition_time - timedelta(hours=1), self.before + ) + + def testInstantBefore(self): + self._test_all( + self.transition_time - self.instant, self.before + ) + + def testTransition(self): + self._test_all( + self.transition_time, self.after + ) + + def testInstantAfter(self): + self._test_all( + self.transition_time + self.instant, self.after + ) + + def testHourAfter(self): + self._test_all( + self.transition_time + timedelta(hours=1), self.after + ) + + def testTwoHoursAfter(self): + self._test_all( + self.transition_time + timedelta(hours=1), self.after + ) + + def testDayAfter(self): + self._test_all( + self.transition_time + timedelta(days=1), self.after + ) + + +class USEasternDSTEndTestCase(USEasternDSTStartTestCase): + tzinfo = pytz.timezone('US/Eastern') + transition_time = datetime(2002, 10, 27, 6, 0, 0, tzinfo=UTC) + before = { + 'tzname': 'EDT', + 'utcoffset': timedelta(hours=-4), + 'dst': timedelta(hours=1), + } + after = { + 'tzname': 'EST', + 'utcoffset': timedelta(hours=-5), + 'dst': timedelta(hours=0), + } + + +class USEasternEPTStartTestCase(USEasternDSTStartTestCase): + transition_time = datetime(1945, 8, 14, 23, 0, 0, tzinfo=UTC) + before = { + 'tzname': 'EWT', + 'utcoffset': timedelta(hours=-4), + 'dst': timedelta(hours=1), + } + after = { + 'tzname': 'EPT', + 'utcoffset': timedelta(hours=-4), + 'dst': timedelta(hours=1), + } + + +class USEasternEPTEndTestCase(USEasternDSTStartTestCase): + transition_time = datetime(1945, 9, 30, 6, 0, 0, tzinfo=UTC) + before = { + 'tzname': 'EPT', + 'utcoffset': timedelta(hours=-4), + 'dst': timedelta(hours=1), + } + after = { + 'tzname': 'EST', + 'utcoffset': timedelta(hours=-5), + 'dst': timedelta(hours=0), + } + + +class WarsawWMTEndTestCase(USEasternDSTStartTestCase): + # In 1915, Warsaw changed from Warsaw to Central European time. + # This involved the clocks being set backwards, causing a end-of-DST + # like situation without DST being involved. + tzinfo = pytz.timezone('Europe/Warsaw') + transition_time = datetime(1915, 8, 4, 22, 36, 0, tzinfo=UTC) + before = { + 'tzname': 'WMT', + 'utcoffset': timedelta(hours=1, minutes=24), + 'dst': timedelta(0), + } + after = { + 'tzname': 'CET', + 'utcoffset': timedelta(hours=1), + 'dst': timedelta(0), + } + + +class VilniusWMTEndTestCase(USEasternDSTStartTestCase): + # At the end of 1916, Vilnius changed timezones putting its clock + # forward by 11 minutes 35 seconds. Neither timezone was in DST mode. + tzinfo = pytz.timezone('Europe/Vilnius') + instant = timedelta(seconds=31) + transition_time = datetime(1916, 12, 31, 22, 36, 00, tzinfo=UTC) + before = { + 'tzname': 'WMT', + 'utcoffset': timedelta(hours=1, minutes=24), + 'dst': timedelta(0), + } + after = { + 'tzname': 'KMT', + 'utcoffset': timedelta(hours=1, minutes=36), # Really 1:35:36 + 'dst': timedelta(0), + } + + +class VilniusCESTStartTestCase(USEasternDSTStartTestCase): + # In 1941, Vilnius changed from MSG to CEST, switching to summer + # time while simultaneously reducing its UTC offset by two hours, + # causing the clocks to go backwards for this summer time + # switchover. + tzinfo = pytz.timezone('Europe/Vilnius') + transition_time = datetime(1941, 6, 23, 21, 00, 00, tzinfo=UTC) + before = { + 'tzname': 'MSK', + 'utcoffset': timedelta(hours=3), + 'dst': timedelta(0), + } + after = { + 'tzname': 'CEST', + 'utcoffset': timedelta(hours=2), + 'dst': timedelta(hours=1), + } + + +class LondonHistoryStartTestCase(USEasternDSTStartTestCase): + # The first known timezone transition in London was in 1847 when + # clocks where synchronized to GMT. However, we currently only + # understand v1 format tzfile(5) files which does handle years + # this far in the past, so our earliest known transition is in + # 1916. + tzinfo = pytz.timezone('Europe/London') + # transition_time = datetime(1847, 12, 1, 1, 15, 00, tzinfo=UTC) + # before = { + # 'tzname': 'LMT', + # 'utcoffset': timedelta(minutes=-75), + # 'dst': timedelta(0), + # } + # after = { + # 'tzname': 'GMT', + # 'utcoffset': timedelta(0), + # 'dst': timedelta(0), + # } + transition_time = datetime(1916, 5, 21, 2, 00, 00, tzinfo=UTC) + before = { + 'tzname': 'GMT', + 'utcoffset': timedelta(0), + 'dst': timedelta(0), + } + after = { + 'tzname': 'BST', + 'utcoffset': timedelta(hours=1), + 'dst': timedelta(hours=1), + } + + +class LondonHistoryEndTestCase(USEasternDSTStartTestCase): + # Timezone switchovers are projected into the future, even + # though no official statements exist or could be believed even + # if they did exist. We currently only check the last known + # transition in 2037, as we are still using v1 format tzfile(5) + # files. + tzinfo = pytz.timezone('Europe/London') + # transition_time = datetime(2499, 10, 25, 1, 0, 0, tzinfo=UTC) + transition_time = datetime(2037, 10, 25, 1, 0, 0, tzinfo=UTC) + before = { + 'tzname': 'BST', + 'utcoffset': timedelta(hours=1), + 'dst': timedelta(hours=1), + } + after = { + 'tzname': 'GMT', + 'utcoffset': timedelta(0), + 'dst': timedelta(0), + } + + +class NoumeaHistoryStartTestCase(USEasternDSTStartTestCase): + # Noumea adopted a whole hour offset in 1912. Previously + # it was 11 hours, 5 minutes and 48 seconds off UTC. However, + # due to limitations of the Python datetime library, we need + # to round that to 11 hours 6 minutes. + tzinfo = pytz.timezone('Pacific/Noumea') + transition_time = datetime(1912, 1, 12, 12, 54, 12, tzinfo=UTC) + before = { + 'tzname': 'LMT', + 'utcoffset': timedelta(hours=11, minutes=6), + 'dst': timedelta(0), + } + after = { + 'tzname': '+11', # pre-2017a, NCT + 'utcoffset': timedelta(hours=11), + 'dst': timedelta(0), + } + + +class NoumeaDSTEndTestCase(USEasternDSTStartTestCase): + # Noumea dropped DST in 1997. + tzinfo = pytz.timezone('Pacific/Noumea') + transition_time = datetime(1997, 3, 1, 15, 00, 00, tzinfo=UTC) + before = { + 'tzname': '+12', # pre-2017a, NCST + 'utcoffset': timedelta(hours=12), + 'dst': timedelta(hours=1), + } + after = { + 'tzname': '+11', # pre-2017a, NCT + 'utcoffset': timedelta(hours=11), + 'dst': timedelta(0), + } + + +class NoumeaNoMoreDSTTestCase(NoumeaDSTEndTestCase): + # Noumea dropped DST in 1997. Here we test that it stops occuring. + transition_time = ( + NoumeaDSTEndTestCase.transition_time + timedelta(days=365 * 10)) + before = NoumeaDSTEndTestCase.after + after = NoumeaDSTEndTestCase.after + + +class TahitiTestCase(USEasternDSTStartTestCase): + # Tahiti has had a single transition in its history. + tzinfo = pytz.timezone('Pacific/Tahiti') + transition_time = datetime(1912, 10, 1, 9, 58, 16, tzinfo=UTC) + before = { + 'tzname': 'LMT', + 'utcoffset': timedelta(hours=-9, minutes=-58), + 'dst': timedelta(0), + } + after = { + 'tzname': '-10', # pre-2017a, TAHT + 'utcoffset': timedelta(hours=-10), + 'dst': timedelta(0), + } + + +class SamoaInternationalDateLineChange(USEasternDSTStartTestCase): + # At the end of 2011, Samoa will switch from being east of the + # international dateline to the west. There will be no Dec 30th + # 2011 and it will switch from UTC-10 to UTC+14. + tzinfo = pytz.timezone('Pacific/Apia') + transition_time = datetime(2011, 12, 30, 10, 0, 0, tzinfo=UTC) + before = { + 'tzname': '-10', # pre-2017a, SDT + 'utcoffset': timedelta(hours=-10), + 'dst': timedelta(hours=1), + } + after = { + 'tzname': '+14', # pre-2017a, WSDT + 'utcoffset': timedelta(hours=14), + 'dst': timedelta(hours=1), + } + + +class ReferenceUSEasternDSTStartTestCase(USEasternDSTStartTestCase): + tzinfo = reference.Eastern + + def test_arithmetic(self): + # Reference implementation cannot handle this + pass + + +class ReferenceUSEasternDSTEndTestCase(USEasternDSTEndTestCase): + tzinfo = reference.Eastern + + def testHourBefore(self): + # Python's datetime library has a bug, where the hour before + # a daylight saving transition is one hour out. For example, + # at the end of US/Eastern daylight saving time, 01:00 EST + # occurs twice (once at 05:00 UTC and once at 06:00 UTC), + # whereas the first should actually be 01:00 EDT. + # Note that this bug is by design - by accepting this ambiguity + # for one hour one hour per year, an is_dst flag on datetime.time + # became unnecessary. + self._test_all(self.transition_time - timedelta(hours=1), self.after) + + def testInstantBefore(self): + self._test_all(self.transition_time - timedelta(seconds=1), self.after) + + def test_arithmetic(self): + # Reference implementation cannot handle this + pass + + +class LocalTestCase(unittest.TestCase): + def testLocalize(self): + loc_tz = pytz.timezone('Europe/Amsterdam') + + loc_time = loc_tz.localize(datetime(1930, 5, 10, 0, 0, 0)) + # Actually +00:19:32, but Python datetime rounds this + self.assertEqual(loc_time.strftime('%Z%z'), 'AMT+0020') + + loc_time = loc_tz.localize(datetime(1930, 5, 20, 0, 0, 0)) + # Actually +00:19:32, but Python datetime rounds this + self.assertEqual(loc_time.strftime('%Z%z'), 'NST+0120') + + loc_time = loc_tz.localize(datetime(1940, 5, 10, 0, 0, 0)) + # pre-2017a, abbreviation was NCT + self.assertEqual(loc_time.strftime('%Z%z'), '+0020+0020') + + loc_time = loc_tz.localize(datetime(1940, 5, 20, 0, 0, 0)) + self.assertEqual(loc_time.strftime('%Z%z'), 'CEST+0200') + + loc_time = loc_tz.localize(datetime(2004, 2, 1, 0, 0, 0)) + self.assertEqual(loc_time.strftime('%Z%z'), 'CET+0100') + + loc_time = loc_tz.localize(datetime(2004, 4, 1, 0, 0, 0)) + self.assertEqual(loc_time.strftime('%Z%z'), 'CEST+0200') + + loc_time = loc_tz.localize(datetime(1943, 3, 29, 1, 59, 59)) + self.assertEqual(loc_time.strftime('%Z%z'), 'CET+0100') + + # Switch to US + loc_tz = pytz.timezone('US/Eastern') + + # End of DST ambiguity check + loc_time = loc_tz.localize(datetime(1918, 10, 27, 1, 59, 59), is_dst=1) + self.assertEqual(loc_time.strftime('%Z%z'), 'EDT-0400') + + loc_time = loc_tz.localize(datetime(1918, 10, 27, 1, 59, 59), is_dst=0) + self.assertEqual(loc_time.strftime('%Z%z'), 'EST-0500') + + self.assertRaises( + pytz.AmbiguousTimeError, + loc_tz.localize, datetime(1918, 10, 27, 1, 59, 59), is_dst=None + ) + + # Start of DST non-existent times + loc_time = loc_tz.localize(datetime(1918, 3, 31, 2, 0, 0), is_dst=0) + self.assertEqual(loc_time.strftime('%Z%z'), 'EST-0500') + + loc_time = loc_tz.localize(datetime(1918, 3, 31, 2, 0, 0), is_dst=1) + self.assertEqual(loc_time.strftime('%Z%z'), 'EDT-0400') + + self.assertRaises( + pytz.NonExistentTimeError, + loc_tz.localize, datetime(1918, 3, 31, 2, 0, 0), is_dst=None + ) + + # Weird changes - war time and peace time both is_dst==True + + loc_time = loc_tz.localize(datetime(1942, 2, 9, 3, 0, 0)) + self.assertEqual(loc_time.strftime('%Z%z'), 'EWT-0400') + + loc_time = loc_tz.localize(datetime(1945, 8, 14, 19, 0, 0)) + self.assertEqual(loc_time.strftime('%Z%z'), 'EPT-0400') + + loc_time = loc_tz.localize(datetime(1945, 9, 30, 1, 0, 0), is_dst=1) + self.assertEqual(loc_time.strftime('%Z%z'), 'EPT-0400') + + loc_time = loc_tz.localize(datetime(1945, 9, 30, 1, 0, 0), is_dst=0) + self.assertEqual(loc_time.strftime('%Z%z'), 'EST-0500') + + # Weird changes - ambiguous time (end-of-DST like) but is_dst==False + for zonename, ambiguous_naive, expected in [ + ('Europe/Warsaw', datetime(1915, 8, 4, 23, 59, 59), + ['1915-08-04 23:59:59 WMT+0124', + '1915-08-04 23:59:59 CET+0100']), + ('Europe/Moscow', datetime(2014, 10, 26, 1, 30), + ['2014-10-26 01:30:00 MSK+0400', + '2014-10-26 01:30:00 MSK+0300'])]: + loc_tz = pytz.timezone(zonename) + self.assertRaises( + pytz.AmbiguousTimeError, + loc_tz.localize, ambiguous_naive, is_dst=None + ) + # Also test non-boolean is_dst in the weird case + for dst in [True, timedelta(1), False, timedelta(0)]: + loc_time = loc_tz.localize(ambiguous_naive, is_dst=dst) + self.assertEqual(loc_time.strftime(fmt), expected[not dst]) + + def testNormalize(self): + tz = pytz.timezone('US/Eastern') + dt = datetime(2004, 4, 4, 7, 0, 0, tzinfo=UTC).astimezone(tz) + dt2 = dt - timedelta(minutes=10) + self.assertEqual( + dt2.strftime('%Y-%m-%d %H:%M:%S %Z%z'), + '2004-04-04 02:50:00 EDT-0400' + ) + + dt2 = tz.normalize(dt2) + self.assertEqual( + dt2.strftime('%Y-%m-%d %H:%M:%S %Z%z'), + '2004-04-04 01:50:00 EST-0500' + ) + + def testPartialMinuteOffsets(self): + # utcoffset in Amsterdam was not a whole minute until 1937 + # However, we fudge this by rounding them, as the Python + # datetime library + tz = pytz.timezone('Europe/Amsterdam') + utc_dt = datetime(1914, 1, 1, 13, 40, 28, tzinfo=UTC) # correct + utc_dt = utc_dt.replace(second=0) # But we need to fudge it + loc_dt = utc_dt.astimezone(tz) + self.assertEqual( + loc_dt.strftime('%Y-%m-%d %H:%M:%S %Z%z'), + '1914-01-01 14:00:00 AMT+0020' + ) + + # And get back... + utc_dt = loc_dt.astimezone(UTC) + self.assertEqual( + utc_dt.strftime('%Y-%m-%d %H:%M:%S %Z%z'), + '1914-01-01 13:40:00 UTC+0000' + ) + + def no_testCreateLocaltime(self): + # It would be nice if this worked, but it doesn't. + tz = pytz.timezone('Europe/Amsterdam') + dt = datetime(2004, 10, 31, 2, 0, 0, tzinfo=tz) + self.assertEqual( + dt.strftime(fmt), + '2004-10-31 02:00:00 CET+0100' + ) + + +class CommonTimezonesTestCase(unittest.TestCase): + def test_bratislava(self): + # Bratislava is the default timezone for Slovakia, but our + # heuristics where not adding it to common_timezones. Ideally, + # common_timezones should be populated from zone.tab at runtime, + # but I'm hesitant to pay the startup cost as loading the list + # on demand whilst remaining backwards compatible seems + # difficult. + self.assertTrue('Europe/Bratislava' in pytz.common_timezones) + self.assertTrue('Europe/Bratislava' in pytz.common_timezones_set) + + def test_us_eastern(self): + self.assertTrue('US/Eastern' in pytz.common_timezones) + self.assertTrue('US/Eastern' in pytz.common_timezones_set) + + def test_belfast(self): + # Belfast uses London time. + self.assertTrue('Europe/Belfast' in pytz.all_timezones_set) + self.assertFalse('Europe/Belfast' in pytz.common_timezones) + self.assertFalse('Europe/Belfast' in pytz.common_timezones_set) + + +class ZoneCaseInsensitivityTestCase(unittest.TestCase): + def test_lower_case_timezone_constructor_arg(self): + for tz in pytz.all_timezones_set: + from_lower = pytz.timezone(tz.lower()) + from_passed = pytz.timezone(tz) + self.assertEqual(from_lower, + from_passed, + "arg '%s' and arg '%s' produce different " + "timezone objects" % ( + from_lower, from_passed)) + + +class BaseTzInfoTestCase: + '''Ensure UTC, StaticTzInfo and DstTzInfo work consistently. + + These tests are run for each type of tzinfo. + ''' + tz = None # override + tz_class = None # override + + def test_expectedclass(self): + self.assertTrue(isinstance(self.tz, self.tz_class)) + + def test_fromutc(self): + # naive datetime. + dt1 = datetime(2011, 10, 31) + + # localized datetime, same timezone. + dt2 = self.tz.localize(dt1) + + # Both should give the same results. Note that the standard + # Python tzinfo.fromutc() only supports the second. + for dt in [dt1, dt2]: + loc_dt = self.tz.fromutc(dt) + loc_dt2 = pytz.utc.localize(dt1).astimezone(self.tz) + self.assertEqual(loc_dt, loc_dt2) + + # localized datetime, different timezone. + new_tz = pytz.timezone('Europe/Paris') + self.assertTrue(self.tz is not new_tz) + dt3 = new_tz.localize(dt1) + self.assertRaises(ValueError, self.tz.fromutc, dt3) + + def test_normalize(self): + other_tz = pytz.timezone('Europe/Paris') + self.assertTrue(self.tz is not other_tz) + + dt = datetime(2012, 3, 26, 12, 0) + other_dt = other_tz.localize(dt) + + local_dt = self.tz.normalize(other_dt) + + self.assertTrue(local_dt.tzinfo is not other_dt.tzinfo) + self.assertNotEqual( + local_dt.replace(tzinfo=None), other_dt.replace(tzinfo=None)) + + def test_astimezone(self): + other_tz = pytz.timezone('Europe/Paris') + self.assertTrue(self.tz is not other_tz) + + dt = datetime(2012, 3, 26, 12, 0) + other_dt = other_tz.localize(dt) + + local_dt = other_dt.astimezone(self.tz) + + self.assertTrue(local_dt.tzinfo is not other_dt.tzinfo) + self.assertNotEqual( + local_dt.replace(tzinfo=None), other_dt.replace(tzinfo=None)) + + +class OptimizedUTCTestCase(unittest.TestCase, BaseTzInfoTestCase): + tz = pytz.utc + tz_class = tz.__class__ + + +class LegacyUTCTestCase(unittest.TestCase, BaseTzInfoTestCase): + # Deprecated timezone, but useful for comparison tests. + tz = pytz.timezone('Etc/UTC') + tz_class = StaticTzInfo + + +class StaticTzInfoTestCase(unittest.TestCase, BaseTzInfoTestCase): + tz = pytz.timezone('GMT') + tz_class = StaticTzInfo + + +class DstTzInfoTestCase(unittest.TestCase, BaseTzInfoTestCase): + tz = pytz.timezone('Australia/Melbourne') + tz_class = DstTzInfo + + +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(doctest.DocTestSuite('pytz')) + suite.addTest(doctest.DocTestSuite('pytz.tzinfo')) + import test_tzinfo + suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(test_tzinfo)) + return suite + + +if __name__ == '__main__': + warnings.simplefilter("error") # Warnings should be fatal in tests. + unittest.main(defaultTest='test_suite') diff --git a/src/pytz/tzfile.py b/src/pytz/tzfile.py new file mode 100644 index 0000000..25117f3 --- /dev/null +++ b/src/pytz/tzfile.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +''' +$Id: tzfile.py,v 1.8 2004/06/03 00:15:24 zenzen Exp $ +''' + +from datetime import datetime +from struct import unpack, calcsize + +from pytz.tzinfo import StaticTzInfo, DstTzInfo, memorized_ttinfo +from pytz.tzinfo import memorized_datetime, memorized_timedelta + + +def _byte_string(s): + """Cast a string or byte string to an ASCII byte string.""" + return s.encode('ASCII') + +_NULL = _byte_string('\0') + + +def _std_string(s): + """Cast a string or byte string to an ASCII string.""" + return str(s.decode('ASCII')) + + +def build_tzinfo(zone, fp): + head_fmt = '>4s c 15x 6l' + head_size = calcsize(head_fmt) + (magic, format, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, + typecnt, charcnt) = unpack(head_fmt, fp.read(head_size)) + + # Make sure it is a tzfile(5) file + assert magic == _byte_string('TZif'), 'Got magic %s' % repr(magic) + + # Read out the transition times, localtime indices and ttinfo structures. + data_fmt = '>%(timecnt)dl %(timecnt)dB %(ttinfo)s %(charcnt)ds' % dict( + timecnt=timecnt, ttinfo='lBB' * typecnt, charcnt=charcnt) + data_size = calcsize(data_fmt) + data = unpack(data_fmt, fp.read(data_size)) + + # make sure we unpacked the right number of values + assert len(data) == 2 * timecnt + 3 * typecnt + 1 + transitions = [memorized_datetime(trans) + for trans in data[:timecnt]] + lindexes = list(data[timecnt:2 * timecnt]) + ttinfo_raw = data[2 * timecnt:-1] + tznames_raw = data[-1] + del data + + # Process ttinfo into separate structs + ttinfo = [] + tznames = {} + i = 0 + while i < len(ttinfo_raw): + # have we looked up this timezone name yet? + tzname_offset = ttinfo_raw[i + 2] + if tzname_offset not in tznames: + nul = tznames_raw.find(_NULL, tzname_offset) + if nul < 0: + nul = len(tznames_raw) + tznames[tzname_offset] = _std_string( + tznames_raw[tzname_offset:nul]) + ttinfo.append((ttinfo_raw[i], + bool(ttinfo_raw[i + 1]), + tznames[tzname_offset])) + i += 3 + + # Now build the timezone object + if len(ttinfo) == 1 or len(transitions) == 0: + ttinfo[0][0], ttinfo[0][2] + cls = type(zone, (StaticTzInfo,), dict( + zone=zone, + _utcoffset=memorized_timedelta(ttinfo[0][0]), + _tzname=ttinfo[0][2])) + else: + # Early dates use the first standard time ttinfo + i = 0 + while ttinfo[i][1]: + i += 1 + if ttinfo[i] == ttinfo[lindexes[0]]: + transitions[0] = datetime.min + else: + transitions.insert(0, datetime.min) + lindexes.insert(0, i) + + # calculate transition info + transition_info = [] + for i in range(len(transitions)): + inf = ttinfo[lindexes[i]] + utcoffset = inf[0] + if not inf[1]: + dst = 0 + else: + for j in range(i - 1, -1, -1): + prev_inf = ttinfo[lindexes[j]] + if not prev_inf[1]: + break + dst = inf[0] - prev_inf[0] # dst offset + + # Bad dst? Look further. DST > 24 hours happens when + # a timzone has moved across the international dateline. + if dst <= 0 or dst > 3600 * 3: + for j in range(i + 1, len(transitions)): + stdinf = ttinfo[lindexes[j]] + if not stdinf[1]: + dst = inf[0] - stdinf[0] + if dst > 0: + break # Found a useful std time. + + tzname = inf[2] + + # Round utcoffset and dst to the nearest minute or the + # datetime library will complain. Conversions to these timezones + # might be up to plus or minus 30 seconds out, but it is + # the best we can do. + utcoffset = int((utcoffset + 30) // 60) * 60 + dst = int((dst + 30) // 60) * 60 + transition_info.append(memorized_ttinfo(utcoffset, dst, tzname)) + + cls = type(zone, (DstTzInfo,), dict( + zone=zone, + _utc_transition_times=transitions, + _transition_info=transition_info)) + + return cls() + +if __name__ == '__main__': + import os.path + from pprint import pprint + base = os.path.join(os.path.dirname(__file__), 'zoneinfo') + tz = build_tzinfo('Australia/Melbourne', + open(os.path.join(base, 'Australia', 'Melbourne'), 'rb')) + tz = build_tzinfo('US/Eastern', + open(os.path.join(base, 'US', 'Eastern'), 'rb')) + pprint(tz._utc_transition_times) diff --git a/src/pytz/tzinfo.py b/src/pytz/tzinfo.py new file mode 100644 index 0000000..725978d --- /dev/null +++ b/src/pytz/tzinfo.py @@ -0,0 +1,577 @@ +'''Base classes and helpers for building zone specific tzinfo classes''' + +from datetime import datetime, timedelta, tzinfo +from bisect import bisect_right +try: + set +except NameError: + from sets import Set as set + +import pytz +from pytz.exceptions import AmbiguousTimeError, NonExistentTimeError + +__all__ = [] + +_timedelta_cache = {} + + +def memorized_timedelta(seconds): + '''Create only one instance of each distinct timedelta''' + try: + return _timedelta_cache[seconds] + except KeyError: + delta = timedelta(seconds=seconds) + _timedelta_cache[seconds] = delta + return delta + +_epoch = datetime.utcfromtimestamp(0) +_datetime_cache = {0: _epoch} + + +def memorized_datetime(seconds): + '''Create only one instance of each distinct datetime''' + try: + return _datetime_cache[seconds] + except KeyError: + # NB. We can't just do datetime.utcfromtimestamp(seconds) as this + # fails with negative values under Windows (Bug #90096) + dt = _epoch + timedelta(seconds=seconds) + _datetime_cache[seconds] = dt + return dt + +_ttinfo_cache = {} + + +def memorized_ttinfo(*args): + '''Create only one instance of each distinct tuple''' + try: + return _ttinfo_cache[args] + except KeyError: + ttinfo = ( + memorized_timedelta(args[0]), + memorized_timedelta(args[1]), + args[2] + ) + _ttinfo_cache[args] = ttinfo + return ttinfo + +_notime = memorized_timedelta(0) + + +def _to_seconds(td): + '''Convert a timedelta to seconds''' + return td.seconds + td.days * 24 * 60 * 60 + + +class BaseTzInfo(tzinfo): + # Overridden in subclass + _utcoffset = None + _tzname = None + zone = None + + def __str__(self): + return self.zone + + +class StaticTzInfo(BaseTzInfo): + '''A timezone that has a constant offset from UTC + + These timezones are rare, as most locations have changed their + offset at some point in their history + ''' + def fromutc(self, dt): + '''See datetime.tzinfo.fromutc''' + if dt.tzinfo is not None and dt.tzinfo is not self: + raise ValueError('fromutc: dt.tzinfo is not self') + return (dt + self._utcoffset).replace(tzinfo=self) + + def utcoffset(self, dt, is_dst=None): + '''See datetime.tzinfo.utcoffset + + is_dst is ignored for StaticTzInfo, and exists only to + retain compatibility with DstTzInfo. + ''' + return self._utcoffset + + def dst(self, dt, is_dst=None): + '''See datetime.tzinfo.dst + + is_dst is ignored for StaticTzInfo, and exists only to + retain compatibility with DstTzInfo. + ''' + return _notime + + def tzname(self, dt, is_dst=None): + '''See datetime.tzinfo.tzname + + is_dst is ignored for StaticTzInfo, and exists only to + retain compatibility with DstTzInfo. + ''' + return self._tzname + + def localize(self, dt, is_dst=False): + '''Convert naive time to local time''' + if dt.tzinfo is not None: + raise ValueError('Not naive datetime (tzinfo is already set)') + return dt.replace(tzinfo=self) + + def normalize(self, dt, is_dst=False): + '''Correct the timezone information on the given datetime. + + This is normally a no-op, as StaticTzInfo timezones never have + ambiguous cases to correct: + + >>> from pytz import timezone + >>> gmt = timezone('GMT') + >>> isinstance(gmt, StaticTzInfo) + True + >>> dt = datetime(2011, 5, 8, 1, 2, 3, tzinfo=gmt) + >>> gmt.normalize(dt) is dt + True + + The supported method of converting between timezones is to use + datetime.astimezone(). Currently normalize() also works: + + >>> la = timezone('America/Los_Angeles') + >>> dt = la.localize(datetime(2011, 5, 7, 1, 2, 3)) + >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' + >>> gmt.normalize(dt).strftime(fmt) + '2011-05-07 08:02:03 GMT (+0000)' + ''' + if dt.tzinfo is self: + return dt + if dt.tzinfo is None: + raise ValueError('Naive time - no tzinfo set') + return dt.astimezone(self) + + def __repr__(self): + return '<StaticTzInfo %r>' % (self.zone,) + + def __reduce__(self): + # Special pickle to zone remains a singleton and to cope with + # database changes. + return pytz._p, (self.zone,) + + +class DstTzInfo(BaseTzInfo): + '''A timezone that has a variable offset from UTC + + The offset might change if daylight saving time comes into effect, + or at a point in history when the region decides to change their + timezone definition. + ''' + # Overridden in subclass + + # Sorted list of DST transition times, UTC + _utc_transition_times = None + + # [(utcoffset, dstoffset, tzname)] corresponding to + # _utc_transition_times entries + _transition_info = None + + zone = None + + # Set in __init__ + + _tzinfos = None + _dst = None # DST offset + + def __init__(self, _inf=None, _tzinfos=None): + if _inf: + self._tzinfos = _tzinfos + self._utcoffset, self._dst, self._tzname = _inf + else: + _tzinfos = {} + self._tzinfos = _tzinfos + self._utcoffset, self._dst, self._tzname = ( + self._transition_info[0]) + _tzinfos[self._transition_info[0]] = self + for inf in self._transition_info[1:]: + if inf not in _tzinfos: + _tzinfos[inf] = self.__class__(inf, _tzinfos) + + def fromutc(self, dt): + '''See datetime.tzinfo.fromutc''' + if (dt.tzinfo is not None and + getattr(dt.tzinfo, '_tzinfos', None) is not self._tzinfos): + raise ValueError('fromutc: dt.tzinfo is not self') + dt = dt.replace(tzinfo=None) + idx = max(0, bisect_right(self._utc_transition_times, dt) - 1) + inf = self._transition_info[idx] + return (dt + inf[0]).replace(tzinfo=self._tzinfos[inf]) + + def normalize(self, dt): + '''Correct the timezone information on the given datetime + + If date arithmetic crosses DST boundaries, the tzinfo + is not magically adjusted. This method normalizes the + tzinfo to the correct one. + + To test, first we need to do some setup + + >>> from pytz import timezone + >>> utc = timezone('UTC') + >>> eastern = timezone('US/Eastern') + >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' + + We next create a datetime right on an end-of-DST transition point, + the instant when the wallclocks are wound back one hour. + + >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc) + >>> loc_dt = utc_dt.astimezone(eastern) + >>> loc_dt.strftime(fmt) + '2002-10-27 01:00:00 EST (-0500)' + + Now, if we subtract a few minutes from it, note that the timezone + information has not changed. + + >>> before = loc_dt - timedelta(minutes=10) + >>> before.strftime(fmt) + '2002-10-27 00:50:00 EST (-0500)' + + But we can fix that by calling the normalize method + + >>> before = eastern.normalize(before) + >>> before.strftime(fmt) + '2002-10-27 01:50:00 EDT (-0400)' + + The supported method of converting between timezones is to use + datetime.astimezone(). Currently, normalize() also works: + + >>> th = timezone('Asia/Bangkok') + >>> am = timezone('Europe/Amsterdam') + >>> dt = th.localize(datetime(2011, 5, 7, 1, 2, 3)) + >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' + >>> am.normalize(dt).strftime(fmt) + '2011-05-06 20:02:03 CEST (+0200)' + ''' + if dt.tzinfo is None: + raise ValueError('Naive time - no tzinfo set') + + # Convert dt in localtime to UTC + offset = dt.tzinfo._utcoffset + dt = dt.replace(tzinfo=None) + dt = dt - offset + # convert it back, and return it + return self.fromutc(dt) + + def localize(self, dt, is_dst=False): + '''Convert naive time to local time. + + This method should be used to construct localtimes, rather + than passing a tzinfo argument to a datetime constructor. + + is_dst is used to determine the correct timezone in the ambigous + period at the end of daylight saving time. + + >>> from pytz import timezone + >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' + >>> amdam = timezone('Europe/Amsterdam') + >>> dt = datetime(2004, 10, 31, 2, 0, 0) + >>> loc_dt1 = amdam.localize(dt, is_dst=True) + >>> loc_dt2 = amdam.localize(dt, is_dst=False) + >>> loc_dt1.strftime(fmt) + '2004-10-31 02:00:00 CEST (+0200)' + >>> loc_dt2.strftime(fmt) + '2004-10-31 02:00:00 CET (+0100)' + >>> str(loc_dt2 - loc_dt1) + '1:00:00' + + Use is_dst=None to raise an AmbiguousTimeError for ambiguous + times at the end of daylight saving time + + >>> try: + ... loc_dt1 = amdam.localize(dt, is_dst=None) + ... except AmbiguousTimeError: + ... print('Ambiguous') + Ambiguous + + is_dst defaults to False + + >>> amdam.localize(dt) == amdam.localize(dt, False) + True + + is_dst is also used to determine the correct timezone in the + wallclock times jumped over at the start of daylight saving time. + + >>> pacific = timezone('US/Pacific') + >>> dt = datetime(2008, 3, 9, 2, 0, 0) + >>> ploc_dt1 = pacific.localize(dt, is_dst=True) + >>> ploc_dt2 = pacific.localize(dt, is_dst=False) + >>> ploc_dt1.strftime(fmt) + '2008-03-09 02:00:00 PDT (-0700)' + >>> ploc_dt2.strftime(fmt) + '2008-03-09 02:00:00 PST (-0800)' + >>> str(ploc_dt2 - ploc_dt1) + '1:00:00' + + Use is_dst=None to raise a NonExistentTimeError for these skipped + times. + + >>> try: + ... loc_dt1 = pacific.localize(dt, is_dst=None) + ... except NonExistentTimeError: + ... print('Non-existent') + Non-existent + ''' + if dt.tzinfo is not None: + raise ValueError('Not naive datetime (tzinfo is already set)') + + # Find the two best possibilities. + possible_loc_dt = set() + for delta in [timedelta(days=-1), timedelta(days=1)]: + loc_dt = dt + delta + idx = max(0, bisect_right( + self._utc_transition_times, loc_dt) - 1) + inf = self._transition_info[idx] + tzinfo = self._tzinfos[inf] + loc_dt = tzinfo.normalize(dt.replace(tzinfo=tzinfo)) + if loc_dt.replace(tzinfo=None) == dt: + possible_loc_dt.add(loc_dt) + + if len(possible_loc_dt) == 1: + return possible_loc_dt.pop() + + # If there are no possibly correct timezones, we are attempting + # to convert a time that never happened - the time period jumped + # during the start-of-DST transition period. + if len(possible_loc_dt) == 0: + # If we refuse to guess, raise an exception. + if is_dst is None: + raise NonExistentTimeError(dt) + + # If we are forcing the pre-DST side of the DST transition, we + # obtain the correct timezone by winding the clock forward a few + # hours. + elif is_dst: + return self.localize( + dt + timedelta(hours=6), is_dst=True) - timedelta(hours=6) + + # If we are forcing the post-DST side of the DST transition, we + # obtain the correct timezone by winding the clock back. + else: + return self.localize( + dt - timedelta(hours=6), + is_dst=False) + timedelta(hours=6) + + # If we get this far, we have multiple possible timezones - this + # is an ambiguous case occuring during the end-of-DST transition. + + # If told to be strict, raise an exception since we have an + # ambiguous case + if is_dst is None: + raise AmbiguousTimeError(dt) + + # Filter out the possiblilities that don't match the requested + # is_dst + filtered_possible_loc_dt = [ + p for p in possible_loc_dt if bool(p.tzinfo._dst) == is_dst + ] + + # Hopefully we only have one possibility left. Return it. + if len(filtered_possible_loc_dt) == 1: + return filtered_possible_loc_dt[0] + + if len(filtered_possible_loc_dt) == 0: + filtered_possible_loc_dt = list(possible_loc_dt) + + # If we get this far, we have in a wierd timezone transition + # where the clocks have been wound back but is_dst is the same + # in both (eg. Europe/Warsaw 1915 when they switched to CET). + # At this point, we just have to guess unless we allow more + # hints to be passed in (such as the UTC offset or abbreviation), + # but that is just getting silly. + # + # Choose the earliest (by UTC) applicable timezone if is_dst=True + # Choose the latest (by UTC) applicable timezone if is_dst=False + # i.e., behave like end-of-DST transition + dates = {} # utc -> local + for local_dt in filtered_possible_loc_dt: + utc_time = ( + local_dt.replace(tzinfo=None) - local_dt.tzinfo._utcoffset) + assert utc_time not in dates + dates[utc_time] = local_dt + return dates[[min, max][not is_dst](dates)] + + def utcoffset(self, dt, is_dst=None): + '''See datetime.tzinfo.utcoffset + + The is_dst parameter may be used to remove ambiguity during DST + transitions. + + >>> from pytz import timezone + >>> tz = timezone('America/St_Johns') + >>> ambiguous = datetime(2009, 10, 31, 23, 30) + + >>> str(tz.utcoffset(ambiguous, is_dst=False)) + '-1 day, 20:30:00' + + >>> str(tz.utcoffset(ambiguous, is_dst=True)) + '-1 day, 21:30:00' + + >>> try: + ... tz.utcoffset(ambiguous) + ... except AmbiguousTimeError: + ... print('Ambiguous') + Ambiguous + + ''' + if dt is None: + return None + elif dt.tzinfo is not self: + dt = self.localize(dt, is_dst) + return dt.tzinfo._utcoffset + else: + return self._utcoffset + + def dst(self, dt, is_dst=None): + '''See datetime.tzinfo.dst + + The is_dst parameter may be used to remove ambiguity during DST + transitions. + + >>> from pytz import timezone + >>> tz = timezone('America/St_Johns') + + >>> normal = datetime(2009, 9, 1) + + >>> str(tz.dst(normal)) + '1:00:00' + >>> str(tz.dst(normal, is_dst=False)) + '1:00:00' + >>> str(tz.dst(normal, is_dst=True)) + '1:00:00' + + >>> ambiguous = datetime(2009, 10, 31, 23, 30) + + >>> str(tz.dst(ambiguous, is_dst=False)) + '0:00:00' + >>> str(tz.dst(ambiguous, is_dst=True)) + '1:00:00' + >>> try: + ... tz.dst(ambiguous) + ... except AmbiguousTimeError: + ... print('Ambiguous') + Ambiguous + + ''' + if dt is None: + return None + elif dt.tzinfo is not self: + dt = self.localize(dt, is_dst) + return dt.tzinfo._dst + else: + return self._dst + + def tzname(self, dt, is_dst=None): + '''See datetime.tzinfo.tzname + + The is_dst parameter may be used to remove ambiguity during DST + transitions. + + >>> from pytz import timezone + >>> tz = timezone('America/St_Johns') + + >>> normal = datetime(2009, 9, 1) + + >>> tz.tzname(normal) + 'NDT' + >>> tz.tzname(normal, is_dst=False) + 'NDT' + >>> tz.tzname(normal, is_dst=True) + 'NDT' + + >>> ambiguous = datetime(2009, 10, 31, 23, 30) + + >>> tz.tzname(ambiguous, is_dst=False) + 'NST' + >>> tz.tzname(ambiguous, is_dst=True) + 'NDT' + >>> try: + ... tz.tzname(ambiguous) + ... except AmbiguousTimeError: + ... print('Ambiguous') + Ambiguous + ''' + if dt is None: + return self.zone + elif dt.tzinfo is not self: + dt = self.localize(dt, is_dst) + return dt.tzinfo._tzname + else: + return self._tzname + + def __repr__(self): + if self._dst: + dst = 'DST' + else: + dst = 'STD' + if self._utcoffset > _notime: + return '<DstTzInfo %r %s+%s %s>' % ( + self.zone, self._tzname, self._utcoffset, dst + ) + else: + return '<DstTzInfo %r %s%s %s>' % ( + self.zone, self._tzname, self._utcoffset, dst + ) + + def __reduce__(self): + # Special pickle to zone remains a singleton and to cope with + # database changes. + return pytz._p, ( + self.zone, + _to_seconds(self._utcoffset), + _to_seconds(self._dst), + self._tzname + ) + + +def unpickler(zone, utcoffset=None, dstoffset=None, tzname=None): + """Factory function for unpickling pytz tzinfo instances. + + This is shared for both StaticTzInfo and DstTzInfo instances, because + database changes could cause a zones implementation to switch between + these two base classes and we can't break pickles on a pytz version + upgrade. + """ + # Raises a KeyError if zone no longer exists, which should never happen + # and would be a bug. + tz = pytz.timezone(zone) + + # A StaticTzInfo - just return it + if utcoffset is None: + return tz + + # This pickle was created from a DstTzInfo. We need to + # determine which of the list of tzinfo instances for this zone + # to use in order to restore the state of any datetime instances using + # it correctly. + utcoffset = memorized_timedelta(utcoffset) + dstoffset = memorized_timedelta(dstoffset) + try: + return tz._tzinfos[(utcoffset, dstoffset, tzname)] + except KeyError: + # The particular state requested in this timezone no longer exists. + # This indicates a corrupt pickle, or the timezone database has been + # corrected violently enough to make this particular + # (utcoffset,dstoffset) no longer exist in the zone, or the + # abbreviation has been changed. + pass + + # See if we can find an entry differing only by tzname. Abbreviations + # get changed from the initial guess by the database maintainers to + # match reality when this information is discovered. + for localized_tz in tz._tzinfos.values(): + if (localized_tz._utcoffset == utcoffset and + localized_tz._dst == dstoffset): + return localized_tz + + # This (utcoffset, dstoffset) information has been removed from the + # zone. Add it back. This might occur when the database maintainers have + # corrected incorrect information. datetime instances using this + # incorrect information will continue to do so, exactly as they were + # before being pickled. This is purely an overly paranoid safety net - I + # doubt this will ever been needed in real life. + inf = (utcoffset, dstoffset, tzname) + tz._tzinfos[inf] = tz.__class__(inf, tz._tzinfos) + return tz._tzinfos[inf] diff --git a/src/pytz/zoneinfo b/src/pytz/zoneinfo new file mode 120000 index 0000000..f169537 --- /dev/null +++ b/src/pytz/zoneinfo @@ -0,0 +1 @@ +../../build/etc/zoneinfo
\ No newline at end of file diff --git a/src/setup.cfg b/src/setup.cfg new file mode 100644 index 0000000..498ec14 --- /dev/null +++ b/src/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +license_file = LICENSE.txt diff --git a/src/setup.py b/src/setup.py new file mode 100644 index 0000000..cc00a3f --- /dev/null +++ b/src/setup.py @@ -0,0 +1,66 @@ +''' +pytz setup script +''' + +import pytz +import os +import os.path + +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + +me = 'Stuart Bishop' +memail = 'stuart@stuartbishop.net' +packages = ['pytz'] +resources = ['zone.tab', 'locales/pytz.pot'] +for dirpath, dirnames, filenames in os.walk(os.path.join('pytz', 'zoneinfo')): + # remove the 'pytz' part of the path + basepath = dirpath.split(os.path.sep, 1)[1] + resources.extend([os.path.join(basepath, filename) + for filename in filenames]) +package_data = {'pytz': resources} + +assert len(resources) > 10, 'zoneinfo files not found!' + +setup( + name='pytz', + version=pytz.VERSION, + zip_safe=True, + description='World timezone definitions, modern and historical', + long_description=open('README.txt', 'r').read(), + author=me, + author_email=memail, + maintainer=me, + maintainer_email=memail, + url='http://pythonhosted.org/pytz', + license='MIT', + keywords=['timezone', 'tzinfo', 'datetime', 'olson', 'time'], + packages=packages, + package_data=package_data, + download_url='https://pypi.org/project/pytz/', + platforms=['Independent'], + classifiers = [ + 'Development Status :: 6 - Mature', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.4', + 'Programming Language :: Python :: 2.5', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.0', + 'Programming Language :: Python :: 3.1', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], +) diff --git a/test_zdump.py b/test_zdump.py new file mode 100644 index 0000000..3baedfc --- /dev/null +++ b/test_zdump.py @@ -0,0 +1,138 @@ +#!/usr/bin/python2.7 + +import os.path +import sys +sys.path.insert(0, os.path.join('build', 'dist')) + +from datetime import datetime, timedelta +import re +from time import strptime +import unittest +import pytz + + +class ZdumpTestCase(unittest.TestCase): + def utc_to_local_check(self, zone, utc_dt, loc_dt, loc_tzname, is_dst): + loc_tz = pytz.timezone(zone) + self.failUnlessEqual( + utc_dt.astimezone(loc_tz).replace(tzinfo=None), + loc_dt.replace(tzinfo=None)) + + def local_to_utc_check(self, zone, utc_dt, loc_dt, loc_tzname, is_dst): + self.failUnlessEqual( + loc_dt.astimezone(pytz.utc).replace(tzinfo=None), + utc_dt.replace(tzinfo=None)) + + +def test_suite(): + testcases = [] + raw_data = open( + os.path.join(os.path.dirname(__file__), 'zdump.out'), 'r').readlines() + last_zone = None + test_class = None + zdump_line_re = re.compile(r'''(?x) + ^([^\s]+) \s+ (.+) \s UT \s+ = \s+ (.+) \s ([^\s]+) \s+ + isdst=(0|1) \s+ gmtoff=[\-\d]+ \s*$ + ''') + for i in range(0, len(raw_data)): + line = raw_data[i] + m = zdump_line_re.search(line) + if m is None: + raise RuntimeError('Dud line %r' % (line,)) + zone, utc_string, loc_string, tzname, is_dst = m.groups() + is_dst = bool(int(is_dst)) + + if zone != last_zone: + classname = zone.replace( + '+', '_plus_').replace('-', '_minus_').replace('/', '_') + test_class = type(classname, (ZdumpTestCase,), {}) + testcases.append(test_class) + last_zone = zone + skip_next_local = False + + utc_dt = datetime( + *strptime(utc_string, '%a %b %d %H:%M:%S %Y')[:6]) + loc_dt = datetime( + *strptime(loc_string, '%a %b %d %H:%M:%S %Y')[:6]) + + def round_dt(loc_dt, utc_dt): + # Urgh - utcoffset() and dst() have to be rounded to the nearest + # minute, so we need to break our tests to match this limitation + real_offset = loc_dt - utc_dt + secs = real_offset.seconds + real_offset.days * 86400 + fake_offset = timedelta(seconds=int((secs + 30) // 60) * 60) + return utc_dt + fake_offset + + loc_dt = round_dt(loc_dt, utc_dt) + + # If the naive time on the next line is less than on this + # line, and we aren't seeing an end-of-dst transition, then + # we can't do our local->utc tests for either this nor the + # next line since we are in an ambiguous time period (ie. + # we have wound back the clock but don't have differing + # is_dst flags to resolve the ambiguity) + skip_local = skip_next_local + skip_next_local = False + try: + m = zdump_line_re.match(raw_data[i + 1]) + except IndexError: + m = None + if m is not None: + (next_zone, next_utc_string, next_loc_string, + next_tzname, next_is_dst) = m.groups() + next_is_dst = bool(int(next_is_dst)) + if next_zone == zone and next_is_dst == is_dst: + next_utc_dt = datetime( + *strptime(next_utc_string, '%a %b %d %H:%M:%S %Y')[:6]) + next_loc_dt = round_dt( + datetime(*strptime( + next_loc_string, '%a %b %d %H:%M:%S %Y')[:6]), + next_utc_dt) + if next_loc_dt <= loc_dt: + skip_local = True + skip_next_local = True + + loc_tz = pytz.timezone(zone) + loc_dt = loc_tz.localize(loc_dt, is_dst) + + utc_dt = pytz.utc.localize(utc_dt) + + test_name = 'test_utc_to_local_%04d_%02d_%02d_%02d_%02d_%02d' % ( + utc_dt.year, utc_dt.month, utc_dt.day, + utc_dt.hour, utc_dt.minute, utc_dt.second) + + def test_utc_to_local( + self, zone=zone, utc_dt=utc_dt, loc_dt=loc_dt, + tzname=tzname, is_dst=is_dst): + self.utc_to_local_check(zone, utc_dt, loc_dt, tzname, is_dst) + test_utc_to_local.__name__ = test_name + setattr(test_class, test_name, test_utc_to_local) + + if not skip_local: + test_name = 'test_local_to_utc_%04d_%02d_%02d_%02d_%02d_%02d' % ( + loc_dt.year, loc_dt.month, loc_dt.day, + loc_dt.hour, loc_dt.minute, loc_dt.second) + if is_dst: + test_name += '_dst' + else: + test_name += '_nodst' + + def test_local_to_utc( + self, zone=zone, utc_dt=utc_dt, loc_dt=loc_dt, + tzname=tzname, is_dst=is_dst): + self.local_to_utc_check(zone, utc_dt, loc_dt, tzname, is_dst) + test_local_to_utc.__name__ = test_name + setattr(test_class, test_name, test_local_to_utc) + + classname = zone.replace( + '+', '_plus_').replace('-', '_minus_').replace('/', '_') + test_class = type(classname, (ZdumpTestCase,), {}) + testcases.append(test_class) + + suite = unittest.TestSuite() + while testcases: + suite.addTest(unittest.makeSuite(testcases.pop())) + return suite + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/tz/.gitignore b/tz/.gitignore new file mode 100644 index 0000000..cf3b825 --- /dev/null +++ b/tz/.gitignore @@ -0,0 +1,26 @@ +# Files intentionally not tracked by Git. +# This file is in the public domain. +*.a +*.asc +*.diff +*.i +*.o +*.orig +*.patch +*.rej +*.tar +*.tar.* +*.txt +*.tzs +*.zi +*~ +ChangeLog +check_* +date +leapseconds +tzselect +version +version.h +yearistype +zdump +zic diff --git a/CONTRIBUTING b/tz/CONTRIBUTING index 01336fc..01336fc 100644 --- a/CONTRIBUTING +++ b/tz/CONTRIBUTING diff --git a/tz/Makefile b/tz/Makefile new file mode 100644 index 0000000..fec0a4f --- /dev/null +++ b/tz/Makefile @@ -0,0 +1,1123 @@ +# Make and install tzdb code and data. + +# This file is in the public domain, so clarified as of +# 2009-05-17 by Arthur David Olson. + +# Package name for the code distribution. +PACKAGE= tzcode + +# Version number for the distribution, overridden in the 'tarballs' rule below. +VERSION= unknown + +# Email address for bug reports. +BUGEMAIL= tz@iana.org + +# DATAFORM selects the data format. +# Available formats represent essentially the same data, albeit +# possibly with minor discrepancies that users are not likely to notice. +# To get new features and the best data right away, use: +# DATAFORM= vanguard +# To wait a while before using new features, to give downstream users +# time to upgrade zic (the default), use: +# DATAFORM= main +# To wait even longer for new features, use: +# DATAFORM= rearguard +DATAFORM= main + +# Change the line below for your timezone (after finding the one you want in +# one of the $(TDATA) source files, or adding it to a source file). +# Alternatively, if you discover you've got the wrong timezone, you can just +# zic -l rightzone +# to correct things. +# Use the command +# make zonenames +# to get a list of the values you can use for LOCALTIME. + +LOCALTIME= GMT + +# The POSIXRULES macro controls interpretation of nonstandard and obsolete +# POSIX-like TZ settings like TZ='EET-2EEST' that lack DST transition rules. +# In the reference implementation, if you want something other than Eastern +# United States time as a template for handling these settings, you can +# change the line below (after finding the timezone you want in the +# one of the $(TDATA) source files, or adding it to a source file). +# A setting like TZ='EET-2EEST' is supposed to use the rules in the +# template file to determine "spring forward" and "fall back" days and +# times; the environment variable itself specifies UT offsets of standard and +# daylight saving time. +# Alternatively, if you discover you've got the wrong timezone, you can just +# zic -p rightzone +# to correct things. +# Use the command +# make zonenames +# to get a list of the values you can use for POSIXRULES. +# +# If POSIXRULES is empty, no template is installed; this is the intended +# future default for POSIXRULES. +# +# Nonempty POSIXRULES is obsolete and should not be relied on, because: +# * It does not work correctly in popular implementations such as GNU/Linux. +# * It does not work in the tzdb implementation for timestamps after 2037. +# * It is incompatible with 'zic -b slim' if POSIXRULES specifies transitions +# at standard time or UT rather than at local time. +# In short, software should avoid ruleless settings like TZ='EET-2EEST' +# and so should not depend on the value of POSIXRULES. + +POSIXRULES= America/New_York + +# Also see TZDEFRULESTRING below, which takes effect only +# if the time zone files cannot be accessed. + + +# Installation locations. +# +# The defaults are suitable for Debian, except that if REDO is +# posix_right or right_posix then files that Debian puts under +# /usr/share/zoneinfo/posix and /usr/share/zoneinfo/right are instead +# put under /usr/share/zoneinfo-posix and /usr/share/zoneinfo-leaps, +# respectively. Problems with the Debian approach are discussed in +# the commentary for the right_posix rule (below). + +# Destination directory, which can be used for staging. +# 'make DESTDIR=/stage install' installs under /stage (e.g., to +# /stage/etc/localtime instead of to /etc/localtime). Files under +# /stage are not intended to work as-is, but can be copied by hand to +# the root directory later. If DESTDIR is empty, 'make install' does +# not stage, but installs directly into production locations. +DESTDIR = + +# Everything is installed into subdirectories of TOPDIR, and used there. +# TOPDIR should be empty (meaning the root directory), +# or a directory name that does not end in "/". +# TOPDIR should be empty or an absolute name unless you're just testing. +TOPDIR = + +# The default local timezone is taken from the file TZDEFAULT. +TZDEFAULT = $(TOPDIR)/etc/localtime + +# The subdirectory containing installed program and data files, and +# likewise for installed files that can be shared among architectures. +# These should be relative file names. +USRDIR = usr +USRSHAREDIR = $(USRDIR)/share + +# "Compiled" timezone information is placed in the "TZDIR" directory +# (and subdirectories). +# TZDIR_BASENAME should not contain "/" and should not be ".", ".." or empty. +TZDIR_BASENAME= zoneinfo +TZDIR = $(TOPDIR)/$(USRSHAREDIR)/$(TZDIR_BASENAME) + +# The "tzselect" and (if you do "make INSTALL") "date" commands go in: +BINDIR = $(TOPDIR)/$(USRDIR)/bin + +# The "zdump" command goes in: +ZDUMPDIR = $(BINDIR) + +# The "zic" command goes in: +ZICDIR = $(TOPDIR)/$(USRDIR)/sbin + +# Manual pages go in subdirectories of. . . +MANDIR = $(TOPDIR)/$(USRSHAREDIR)/man + +# Library functions are put in an archive in LIBDIR. +LIBDIR = $(TOPDIR)/$(USRDIR)/lib + + +# Types to try, as an alternative to time_t. +TIME_T_ALTERNATIVES = $(TIME_T_ALTERNATIVES_HEAD) $(TIME_T_ALTERNATIVES_TAIL) +TIME_T_ALTERNATIVES_HEAD = int64_t +TIME_T_ALTERNATIVES_TAIL = int32_t uint32_t uint64_t + +# What kind of TZif data files to generate. (TZif is the binary time +# zone data format that zic generates; see Internet RFC 8536.) +# If you want only POSIX time, with time values interpreted as +# seconds since the epoch (not counting leap seconds), use +# REDO= posix_only +# below. If you want only "right" time, with values interpreted +# as seconds since the epoch (counting leap seconds), use +# REDO= right_only +# below. If you want both sets of data available, with leap seconds not +# counted normally, use +# REDO= posix_right +# below. If you want both sets of data available, with leap seconds counted +# normally, use +# REDO= right_posix +# below. POSIX mandates that leap seconds not be counted; for compatibility +# with it, use "posix_only" or "posix_right". Use POSIX time on systems with +# leap smearing; this can work better than unsmeared "right" time with +# applications that are not leap second aware, and is closer to unsmeared +# "right" time than unsmeared POSIX time is (e.g., 0.5 vs 1.0 s max error). + +REDO= posix_right + +# To install data in text form that has all the information of the TZif data, +# (optionally incorporating leap second information), use +# TZDATA_TEXT= tzdata.zi leapseconds +# To install text data without leap second information (e.g., because +# REDO='posix_only'), use +# TZDATA_TEXT= tzdata.zi +# To avoid installing text data, use +# TZDATA_TEXT= + +TZDATA_TEXT= leapseconds tzdata.zi + +# For backward-compatibility links for old zone names, use +# BACKWARD= backward +# If you also want the link US/Pacific-New, even though it is confusing +# and is planned to be removed from the database eventually, use +# BACKWARD= backward pacificnew +# To omit these links, use +# BACKWARD= + +BACKWARD= backward + +# If you want out-of-scope and often-wrong data from the file 'backzone', use +# PACKRATDATA= backzone +# To omit this data, use +# PACKRATDATA= + +PACKRATDATA= + +# The name of a locale using the UTF-8 encoding, used during self-tests. +# The tests are skipped if the name does not appear to work on this system. + +UTF8_LOCALE= en_US.utf8 + +# Since "." may not be in PATH... + +YEARISTYPE= ./yearistype + +# Non-default libraries needed to link. +LDLIBS= + +# Add the following to the end of the "CFLAGS=" line as needed to override +# defaults specified in the source code. "-DFOO" is equivalent to "-DFOO=1". +# -DDEPRECATE_TWO_DIGIT_YEARS for optional runtime warnings about strftime +# formats that generate only the last two digits of year numbers +# -DEPOCH_LOCAL if the 'time' function returns local time not UT +# -DEPOCH_OFFSET=N if the 'time' function returns a value N greater +# than what POSIX specifies, assuming local time is UT. +# For example, N is 252460800 on AmigaOS. +# -DHAVE_DECL_ASCTIME_R=0 if <time.h> does not declare asctime_r +# -DHAVE_DECL_ENVIRON if <unistd.h> declares 'environ' +# -DHAVE_DIRECT_H if mkdir needs <direct.h> (MS-Windows) +# -DHAVE_GENERIC=0 if _Generic does not work +# -DHAVE_GETTEXT if 'gettext' works (e.g., GNU/Linux, FreeBSD, Solaris) +# -DHAVE_INCOMPATIBLE_CTIME_R if your system's time.h declares +# ctime_r and asctime_r incompatibly with the POSIX standard +# (Solaris when _POSIX_PTHREAD_SEMANTICS is not defined). +# -DHAVE_INTTYPES_H if you have a non-C99 compiler with <inttypes.h> +# -DHAVE_LINK=0 if your system lacks a link function +# -DHAVE_LOCALTIME_R=0 if your system lacks a localtime_r function +# -DHAVE_LOCALTIME_RZ=0 if you do not want zdump to use localtime_rz +# localtime_rz can make zdump significantly faster, but is nonstandard. +# -DHAVE_POSIX_DECLS=0 if your system's include files do not declare +# functions like 'link' or variables like 'tzname' required by POSIX +# -DHAVE_SNPRINTF=0 if your system lacks the snprintf function +# -DHAVE_STDBOOL_H if you have a non-C99 compiler with <stdbool.h> +# -DHAVE_STDINT_H if you have a non-C99 compiler with <stdint.h> +# -DHAVE_STRFTIME_L if <time.h> declares locale_t and strftime_l +# -DHAVE_STRDUP=0 if your system lacks the strdup function +# -DHAVE_STRTOLL=0 if your system lacks the strtoll function +# -DHAVE_SYMLINK=0 if your system lacks the symlink function +# -DHAVE_SYS_STAT_H=0 if your compiler lacks a <sys/stat.h> +# -DHAVE_SYS_WAIT_H=0 if your compiler lacks a <sys/wait.h> +# -DHAVE_TZSET=0 if your system lacks a tzset function +# -DHAVE_UNISTD_H=0 if your compiler lacks a <unistd.h> +# -Dlocale_t=XXX if your system uses XXX instead of locale_t +# -DRESERVE_STD_EXT_IDS if your platform reserves standard identifiers +# with external linkage, e.g., applications cannot define 'localtime'. +# -Dssize_t=long on hosts like MS-Windows that lack ssize_t +# -DSUPPRESS_TZDIR to not prepend TZDIR to file names; this has +# security implications and is not recommended for general use +# -DTHREAD_SAFE to make localtime.c thread-safe, as POSIX requires; +# not needed by the main-program tz code, which is single-threaded. +# Append other compiler flags as needed, e.g., -pthread on GNU/Linux. +# -Dtime_tz=\"T\" to use T as the time_t type, rather than the system time_t +# This is intended for internal use only; it mangles external names. +# -DTZ_DOMAIN=\"foo\" to use "foo" for gettext domain name; default is "tz" +# -DTZ_DOMAINDIR=\"/path\" to use "/path" for gettext directory; +# the default is system-supplied, typically "/usr/lib/locale" +# -DTZDEFRULESTRING=\",date/time,date/time\" to default to the specified +# DST transitions if the time zone files cannot be accessed +# -DUNINIT_TRAP if reading uninitialized storage can cause problems +# other than simply getting garbage data +# -DUSE_LTZ=0 to build zdump with the system time zone library +# Also set TZDOBJS=zdump.o and CHECK_TIME_T_ALTERNATIVES= below. +# -DZIC_BLOAT_DEFAULT=\"slim\" to default zic's -b option to "slim", and +# similarly for "fat". Fat TZif files work around incompatibilities +# and bugs in some TZif readers, notably readers that mishandle 64-bit +# data in TZif files. Slim TZif files are more efficient and do not +# work around these incompatibilities and bugs. If not given, the +# current default is "fat" but this is intended to change as readers +# requiring fat files often mishandle timestamps after 2037 anyway. +# -DZIC_MAX_ABBR_LEN_WO_WARN=3 +# (or some other number) to set the maximum time zone abbreviation length +# that zic will accept without a warning (the default is 6) +# $(GCC_DEBUG_FLAGS) if you are using recent GCC and want lots of checking +# Select instrumentation via "make GCC_INSTRUMENT='whatever'". +GCC_INSTRUMENT = \ + -fsanitize=undefined -fsanitize-address-use-after-scope \ + -fsanitize-undefined-trap-on-error -fstack-protector +GCC_DEBUG_FLAGS = -DGCC_LINT -g3 -O3 -fno-common \ + $(GCC_INSTRUMENT) \ + -Wall -Wextra \ + -Walloc-size-larger-than=100000 -Warray-bounds=2 \ + -Wbad-function-cast -Wcast-align=strict -Wdate-time \ + -Wdeclaration-after-statement -Wdouble-promotion \ + -Wformat=2 -Wformat-overflow=2 -Wformat-signedness -Wformat-truncation \ + -Winit-self -Wjump-misses-init -Wlogical-op \ + -Wmissing-declarations -Wmissing-prototypes -Wnested-externs \ + -Wold-style-definition -Woverlength-strings -Wpointer-arith \ + -Wshadow -Wshift-overflow=2 -Wstrict-prototypes -Wstringop-overflow=4 \ + -Wstringop-truncation -Wsuggest-attribute=cold \ + -Wsuggest-attribute=const -Wsuggest-attribute=format \ + -Wsuggest-attribute=malloc \ + -Wsuggest-attribute=noreturn -Wsuggest-attribute=pure \ + -Wtrampolines -Wundef -Wuninitialized -Wunused \ + -Wvariadic-macros -Wvla -Wwrite-strings \ + -Wno-address -Wno-format-nonliteral -Wno-sign-compare \ + -Wno-type-limits -Wno-unused-parameter +# +# If your system has a "GMT offset" field in its "struct tm"s +# (or if you decide to add such a field in your system's "time.h" file), +# add the name to a define such as +# -DTM_GMTOFF=tm_gmtoff +# to the end of the "CFLAGS=" line. If not defined, the code attempts to +# guess TM_GMTOFF from other macros; define NO_TM_GMTOFF to suppress this. +# Similarly, if your system has a "zone abbreviation" field, define +# -DTM_ZONE=tm_zone +# and define NO_TM_ZONE to suppress any guessing. These two fields are not +# required by POSIX, but are widely available on GNU/Linux and BSD systems. +# +# The next batch of options control support for external variables +# exported by tzcode. In practice these variables are less useful +# than TM_GMTOFF and TM_ZONE. However, most of them are standardized. +# # +# # To omit or support the external variable "tzname", add one of: +# # -DHAVE_TZNAME=0 +# # -DHAVE_TZNAME=1 +# # to the "CFLAGS=" line. "tzname" is required by POSIX 1988 and later. +# # If not defined, the code attempts to guess HAVE_TZNAME from other macros. +# # Warning: unless time_tz is also defined, HAVE_TZNAME=1 can cause +# # crashes when combined with some platforms' standard libraries, +# # presumably due to memory allocation issues. +# # +# # To omit or support the external variables "timezone" and "daylight", add +# # -DUSG_COMPAT=0 +# # -DUSG_COMPAT=1 +# # to the "CFLAGS=" line; "timezone" and "daylight" are inspired by +# # Unix Systems Group code and are required by POSIX 2008 (with XSI) and later. +# # If not defined, the code attempts to guess USG_COMPAT from other macros. +# # +# # To support the external variable "altzone", add +# # -DALTZONE +# # to the end of the "CFLAGS=" line; although "altzone" appeared in +# # System V Release 3.1 it has not been standardized. +# +# If you want functions that were inspired by early versions of X3J11's work, +# add +# -DSTD_INSPIRED +# to the end of the "CFLAGS=" line. This arranges for the functions +# "tzsetwall", "offtime", "timelocal", "timegm", "timeoff", +# "posix2time", and "time2posix" to be added to the time conversion library. +# "tzsetwall" is like "tzset" except that it arranges for local wall clock +# time (rather than the timezone specified in the TZ environment variable) +# to be used. +# "offtime" is like "gmtime" except that it accepts a second (long) argument +# that gives an offset to add to the time_t when converting it. +# "timelocal" is equivalent to "mktime". +# "timegm" is like "timelocal" except that it turns a struct tm into +# a time_t using UT (rather than local time as "timelocal" does). +# "timeoff" is like "timegm" except that it accepts a second (long) argument +# that gives an offset to use when converting to a time_t. +# "posix2time" and "time2posix" are described in an included manual page. +# X3J11's work does not describe any of these functions. +# Sun has provided "tzsetwall", "timelocal", and "timegm" in SunOS 4.0. +# These functions may well disappear in future releases of the time +# conversion package. +# +# If you don't want functions that were inspired by NetBSD, add +# -DNETBSD_INSPIRED=0 +# to the end of the "CFLAGS=" line. Otherwise, the functions +# "localtime_rz", "mktime_z", "tzalloc", and "tzfree" are added to the +# time library, and if STD_INSPIRED is also defined the functions +# "posix2time_z" and "time2posix_z" are added as well. +# The functions ending in "_z" (or "_rz") are like their unsuffixed +# (or suffixed-by-"_r") counterparts, except with an extra first +# argument of opaque type timezone_t that specifies the timezone. +# "tzalloc" allocates a timezone_t value, and "tzfree" frees it. +# +# If you want to allocate state structures in localtime, add +# -DALL_STATE +# to the end of the "CFLAGS=" line. Storage is obtained by calling malloc. +# +# NIST-PCTS:151-2, Version 1.4, (1993-12-03) is a test suite put +# out by the National Institute of Standards and Technology +# which claims to test C and Posix conformance. If you want to pass PCTS, add +# -DPCTS +# to the end of the "CFLAGS=" line. +# +# If you want strict compliance with XPG4 as of 1994-04-09, add +# -DXPG4_1994_04_09 +# to the end of the "CFLAGS=" line. This causes "strftime" to always return +# 53 as a week number (rather than 52 or 53) for January days before +# January's first Monday when a "%V" format is used and January 1 +# falls on a Friday, Saturday, or Sunday. + +CFLAGS= + +# Linker flags. Default to $(LFLAGS) for backwards compatibility +# to release 2012h and earlier. + +LDFLAGS= $(LFLAGS) + +# For leap seconds, this Makefile uses LEAPSECONDS='-L leapseconds' in +# submake command lines. The default is no leap seconds. + +LEAPSECONDS= + +# The zic command and its arguments. + +zic= ./zic +ZIC= $(zic) $(ZFLAGS) + +# To shrink the size of installed TZif files, +# append "-r @N" to omit data before N-seconds-after-the-Epoch. +# You can also append "-b slim" if that is not already the default; +# see ZIC_BLOAT_DEFAULT above. +# See the zic man page for more about -b and -r. +ZFLAGS= + +# How to use zic to install TZif files. + +ZIC_INSTALL= $(ZIC) -d '$(DESTDIR)$(TZDIR)' $(LEAPSECONDS) + +# The name of a Posix-compliant 'awk' on your system. +# Older 'mawk' versions, such as the 'mawk' in Ubuntu 16.04, might dump core; +# on Ubuntu you can work around this with +# AWK= gawk +AWK= awk + +# The full path name of a Posix-compliant shell, preferably one that supports +# the Korn shell's 'select' statement as an extension. +# These days, Bash is the most popular. +# It should be OK to set this to /bin/sh, on platforms where /bin/sh +# lacks 'select' or doesn't completely conform to Posix, but /bin/bash +# is typically nicer if it works. +KSHELL= /bin/bash + +# Name of curl <https://curl.haxx.se/>, used for HTML validation. +CURL= curl + +# Name of GNU Privacy Guard <https://gnupg.org/>, used to sign distributions. +GPG= gpg + +# The path where SGML DTDs are kept and the catalog file(s) to use when +# validating HTML 4.01. The default should work on both Debian and Red Hat. +SGML_TOPDIR= /usr +SGML_DTDDIR= $(SGML_TOPDIR)/share/xml/w3c-sgml-lib/schema/dtd +SGML_SEARCH_PATH= $(SGML_DTDDIR)/REC-html401-19991224 +SGML_CATALOG_FILES= \ + $(SGML_TOPDIR)/share/doc/w3-recs/html/www.w3.org/TR/1999/REC-html401-19991224/HTML4.cat:$(SGML_TOPDIR)/share/sgml/html/4.01/HTML4.cat + +# The name, arguments and environment of a program to validate HTML 4.01. +# See <http://openjade.sourceforge.net/doc/> for a validator, and +# <https://validator.w3.org/source/> for a validation library. +# Set VALIDATE=':' if you do not have such a program. +VALIDATE = nsgmls +VALIDATE_FLAGS = -s -B -wall -wno-unused-param +VALIDATE_ENV = \ + SGML_CATALOG_FILES='$(SGML_CATALOG_FILES)' \ + SGML_SEARCH_PATH='$(SGML_SEARCH_PATH)' \ + SP_CHARSET_FIXED=YES \ + SP_ENCODING=UTF-8 + +# This expensive test requires USE_LTZ. +# To suppress it, define this macro to be empty. +CHECK_TIME_T_ALTERNATIVES = check_time_t_alternatives + +# SAFE_CHAR is a regular expression that matches a safe character. +# Some parts of this distribution are limited to safe characters; +# others can use any UTF-8 character. +# For now, the safe characters are a safe subset of ASCII. +# The caller must set the shell variable 'sharp' to the character '#', +# since Makefile macros cannot contain '#'. +# TAB_CHAR is a single tab character, in single quotes. +TAB_CHAR= ' ' +SAFE_CHARSET1= $(TAB_CHAR)' !\"'$$sharp'$$%&'\''()*+,./0123456789:;<=>?@' +SAFE_CHARSET2= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\^_`' +SAFE_CHARSET3= 'abcdefghijklmnopqrstuvwxyz{|}~' +SAFE_CHARSET= $(SAFE_CHARSET1)$(SAFE_CHARSET2)$(SAFE_CHARSET3) +SAFE_CHAR= '[]'$(SAFE_CHARSET)'-]' + +# These characters are Latin-1, and so are likely to be displayable +# even in editors with limited character sets. +UNUSUAL_OK_LATIN_1 = «°±»½¾× +# This IPA symbol is represented in Unicode as the composition of +# U+0075 and U+032F, and U+032F is not considered alphabetic by some +# grep implementations that do not grok composition. +UNUSUAL_OK_IPA = u̯ +# Non-ASCII non-letters that OK_CHAR allows, as these characters are +# useful in commentary. +UNUSUAL_OK_CHARSET= $(UNUSUAL_OK_LATIN_1)$(UNUSUAL_OK_IPA) + +# OK_CHAR matches any character allowed in the distributed files. +# This is the same as SAFE_CHAR, except that UNUSUAL_OK_CHARSET and +# multibyte letters are also allowed so that commentary can contain a +# few safe symbols and people's names and can quote non-English sources. +# Other non-letters are limited to ASCII renderings for the +# convenience of maintainers using XEmacs 21.5.34, which by default +# mishandles Unicode characters U+0100 and greater. +OK_CHAR= '[][:alpha:]$(UNUSUAL_OK_CHARSET)'$(SAFE_CHARSET)'-]' + +# SAFE_LINE matches a line of safe characters. +# SAFE_SHARP_LINE is similar, except any OK character can follow '#'; +# this is so that comments can contain non-ASCII characters. +# OK_LINE matches a line of OK characters. +SAFE_LINE= '^'$(SAFE_CHAR)'*$$' +SAFE_SHARP_LINE='^'$(SAFE_CHAR)'*('$$sharp$(OK_CHAR)'*)?$$' +OK_LINE= '^'$(OK_CHAR)'*$$' + +# Flags to give 'tar' when making a distribution. +# Try to use flags appropriate for GNU tar. +GNUTARFLAGS= --numeric-owner --owner=0 --group=0 --mode=go+u,go-w --sort=name +TARFLAGS= `if tar $(GNUTARFLAGS) --version >/dev/null 2>&1; \ + then echo $(GNUTARFLAGS); \ + else :; \ + fi` + +# Flags to give 'gzip' when making a distribution. +GZIPFLAGS= -9n + +############################################################################### + +#MAKE= make + +cc= cc +CC= $(cc) -DTZDIR='"$(TZDIR)"' + +AR= ar + +# ':' on typical hosts; 'ranlib' on the ancient hosts that still need ranlib. +RANLIB= : + +TZCOBJS= zic.o +TZDOBJS= zdump.o localtime.o asctime.o strftime.o +DATEOBJS= date.o localtime.o strftime.o asctime.o +LIBSRCS= localtime.c asctime.c difftime.c +LIBOBJS= localtime.o asctime.o difftime.o +HEADERS= tzfile.h private.h +NONLIBSRCS= zic.c zdump.c +NEWUCBSRCS= date.c strftime.c +SOURCES= $(HEADERS) $(LIBSRCS) $(NONLIBSRCS) $(NEWUCBSRCS) \ + tzselect.ksh workman.sh +MANS= newctime.3 newstrftime.3 newtzset.3 time2posix.3 \ + tzfile.5 tzselect.8 zic.8 zdump.8 +MANTXTS= newctime.3.txt newstrftime.3.txt newtzset.3.txt \ + time2posix.3.txt \ + tzfile.5.txt tzselect.8.txt zic.8.txt zdump.8.txt \ + date.1.txt +COMMON= calendars CONTRIBUTING LICENSE Makefile \ + NEWS README theory.html version +WEB_PAGES= tz-art.html tz-how-to.html tz-link.html +CHECK_WEB_PAGES=check_theory.html check_tz-art.html \ + check_tz-how-to.html check_tz-link.html +DOCS= $(MANS) date.1 $(MANTXTS) $(WEB_PAGES) +PRIMARY_YDATA= africa antarctica asia australasia \ + europe northamerica southamerica +YDATA= $(PRIMARY_YDATA) etcetera +NDATA= systemv factory +TDATA_TO_CHECK= $(YDATA) $(NDATA) backward pacificnew +TDATA= $(YDATA) $(NDATA) $(BACKWARD) +ZONETABLES= zone1970.tab zone.tab +TABDATA= iso3166.tab $(TZDATA_TEXT) $(ZONETABLES) +LEAP_DEPS= leapseconds.awk leap-seconds.list +TZDATA_ZI_DEPS= ziguard.awk zishrink.awk version $(TDATA) $(PACKRATDATA) +DSTDATA_ZI_DEPS= ziguard.awk $(TDATA) $(PACKRATDATA) +DATA= $(TDATA_TO_CHECK) backzone iso3166.tab leap-seconds.list \ + leapseconds yearistype.sh $(ZONETABLES) +AWK_SCRIPTS= checklinks.awk checktab.awk leapseconds.awk \ + ziguard.awk zishrink.awk +MISC= $(AWK_SCRIPTS) zoneinfo2tdf.pl +TZS_YEAR= 2050 +TZS_CUTOFF_FLAG= -c $(TZS_YEAR) +TZS= to$(TZS_YEAR).tzs +TZS_NEW= to$(TZS_YEAR)new.tzs +TZS_DEPS= $(PRIMARY_YDATA) asctime.c localtime.c \ + private.h tzfile.h zdump.c zic.c +# EIGHT_YARDS is just a yard short of the whole ENCHILADA. +EIGHT_YARDS = $(COMMON) $(DOCS) $(SOURCES) $(DATA) $(MISC) tzdata.zi +ENCHILADA = $(EIGHT_YARDS) $(TZS) + +# Consult these files when deciding whether to rebuild the 'version' file. +# This list is not the same as the output of 'git ls-files', since +# .gitignore is not distributed. +VERSION_DEPS= \ + calendars CONTRIBUTING LICENSE Makefile NEWS README \ + africa antarctica asctime.c asia australasia \ + backward backzone \ + checklinks.awk checktab.awk \ + date.1 date.c difftime.c \ + etcetera europe factory iso3166.tab \ + leap-seconds.list leapseconds.awk localtime.c \ + newctime.3 newstrftime.3 newtzset.3 northamerica \ + pacificnew private.h \ + southamerica strftime.c systemv theory.html \ + time2posix.3 tz-art.html tz-how-to.html tz-link.html \ + tzfile.5 tzfile.h tzselect.8 tzselect.ksh \ + workman.sh yearistype.sh \ + zdump.8 zdump.c zic.8 zic.c \ + ziguard.awk zishrink.awk \ + zone.tab zone1970.tab zoneinfo2tdf.pl + +# And for the benefit of csh users on systems that assume the user +# shell should be used to handle commands in Makefiles. . . + +SHELL= /bin/sh + +all: tzselect yearistype zic zdump libtz.a $(TABDATA) \ + vanguard.zi main.zi rearguard.zi + +ALL: all date $(ENCHILADA) + +install: all $(DATA) $(REDO) $(MANS) + mkdir -p '$(DESTDIR)$(BINDIR)' \ + '$(DESTDIR)$(ZDUMPDIR)' '$(DESTDIR)$(ZICDIR)' \ + '$(DESTDIR)$(LIBDIR)' \ + '$(DESTDIR)$(MANDIR)/man3' '$(DESTDIR)$(MANDIR)/man5' \ + '$(DESTDIR)$(MANDIR)/man8' + $(ZIC_INSTALL) -l $(LOCALTIME) \ + `case '$(POSIXRULES)' in ?*) echo '-p';; esac \ + ` $(POSIXRULES) \ + -t '$(DESTDIR)$(TZDEFAULT)' + cp -f $(TABDATA) '$(DESTDIR)$(TZDIR)/.' + cp tzselect '$(DESTDIR)$(BINDIR)/.' + cp zdump '$(DESTDIR)$(ZDUMPDIR)/.' + cp zic '$(DESTDIR)$(ZICDIR)/.' + cp libtz.a '$(DESTDIR)$(LIBDIR)/.' + $(RANLIB) '$(DESTDIR)$(LIBDIR)/libtz.a' + cp -f newctime.3 newtzset.3 '$(DESTDIR)$(MANDIR)/man3/.' + cp -f tzfile.5 '$(DESTDIR)$(MANDIR)/man5/.' + cp -f tzselect.8 zdump.8 zic.8 '$(DESTDIR)$(MANDIR)/man8/.' + +INSTALL: ALL install date.1 + mkdir -p '$(DESTDIR)$(BINDIR)' '$(DESTDIR)$(MANDIR)/man1' + cp date '$(DESTDIR)$(BINDIR)/.' + cp -f date.1 '$(DESTDIR)$(MANDIR)/man1/.' + +version: $(VERSION_DEPS) + { (type git) >/dev/null 2>&1 && \ + V=`git describe --match '[0-9][0-9][0-9][0-9][a-z]*' \ + --abbrev=7 --dirty` || \ + V='$(VERSION)'; } && \ + printf '%s\n' "$$V" >$@.out + mv $@.out $@ + +# These files can be tailored by setting BACKWARD and PACKRATDATA. +vanguard.zi main.zi rearguard.zi: $(DSTDATA_ZI_DEPS) + $(AWK) -v DATAFORM=`expr $@ : '\(.*\).zi'` -f ziguard.awk \ + $(TDATA) $(PACKRATDATA) >$@.out + mv $@.out $@ +# This file has a version comment that attempts to capture any tailoring +# via BACKWARD, DATAFORM, PACKRATDATA, and REDO. +tzdata.zi: $(DATAFORM).zi version zishrink.awk + version=`sed 1q version` && \ + LC_ALL=C $(AWK) \ + -v dataform='$(DATAFORM)' \ + -v deps='$(DSTDATA_ZI_DEPS) zishrink.awk' \ + -v redo='$(REDO)' \ + -v version="$$version" \ + -f zishrink.awk \ + $(DATAFORM).zi >$@.out + mv $@.out $@ + +version.h: version + VERSION=`cat version` && printf '%s\n' \ + 'static char const PKGVERSION[]="($(PACKAGE)) ";' \ + "static char const TZVERSION[]=\"$$VERSION\";" \ + 'static char const REPORT_BUGS_TO[]="$(BUGEMAIL)";' \ + >$@.out + mv $@.out $@ + +zdump: $(TZDOBJS) + $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $(TZDOBJS) $(LDLIBS) + +zic: $(TZCOBJS) + $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $(TZCOBJS) $(LDLIBS) + +yearistype: yearistype.sh + cp yearistype.sh yearistype + chmod +x yearistype + +leapseconds: $(LEAP_DEPS) + $(AWK) -f leapseconds.awk leap-seconds.list >$@.out + mv $@.out $@ + +# Arguments to pass to submakes of install_data. +# They can be overridden by later submake arguments. +INSTALLARGS = \ + BACKWARD='$(BACKWARD)' \ + DESTDIR='$(DESTDIR)' \ + LEAPSECONDS='$(LEAPSECONDS)' \ + PACKRATDATA='$(PACKRATDATA)' \ + TZDEFAULT='$(TZDEFAULT)' \ + TZDIR='$(TZDIR)' \ + YEARISTYPE='$(YEARISTYPE)' \ + ZIC='$(ZIC)' + +INSTALL_DATA_DEPS = zic leapseconds yearistype tzdata.zi + +# 'make install_data' installs one set of TZif files. +install_data: $(INSTALL_DATA_DEPS) + $(ZIC_INSTALL) tzdata.zi + +posix_only: $(INSTALL_DATA_DEPS) + $(MAKE) $(INSTALLARGS) LEAPSECONDS= install_data + +right_only: $(INSTALL_DATA_DEPS) + $(MAKE) $(INSTALLARGS) LEAPSECONDS='-L leapseconds' \ + install_data + +# In earlier versions of this makefile, the other two directories were +# subdirectories of $(TZDIR). However, this led to configuration errors. +# For example, with posix_right under the earlier scheme, +# TZ='right/Australia/Adelaide' got you localtime with leap seconds, +# but gmtime without leap seconds, which led to problems with applications +# like sendmail that subtract gmtime from localtime. +# Therefore, the other two directories are now siblings of $(TZDIR). +# You must replace all of $(TZDIR) to switch from not using leap seconds +# to using them, or vice versa. +right_posix: right_only + rm -fr '$(DESTDIR)$(TZDIR)-leaps' + ln -s '$(TZDIR_BASENAME)' '$(DESTDIR)$(TZDIR)-leaps' || \ + $(MAKE) $(INSTALLARGS) TZDIR='$(TZDIR)-leaps' right_only + $(MAKE) $(INSTALLARGS) TZDIR='$(TZDIR)-posix' posix_only + +posix_right: posix_only + rm -fr '$(DESTDIR)$(TZDIR)-posix' + ln -s '$(TZDIR_BASENAME)' '$(DESTDIR)$(TZDIR)-posix' || \ + $(MAKE) $(INSTALLARGS) TZDIR='$(TZDIR)-posix' posix_only + $(MAKE) $(INSTALLARGS) TZDIR='$(TZDIR)-leaps' right_only + +# This obsolescent rule is present for backwards compatibility with +# tz releases 2014g through 2015g. It should go away eventually. +posix_packrat: $(INSTALL_DATA_DEPS) + $(MAKE) $(INSTALLARGS) PACKRATDATA=backzone posix_only + +zones: $(REDO) + +# dummy.zd is not a real file; it is mentioned here only so that the +# top-level 'make' does not have a syntax error. +ZDS = dummy.zd +# Rule used only by submakes invoked by the $(TZS_NEW) rule. +# It is separate so that GNU 'make -j' can run instances in parallel. +$(ZDS): zdump + ./zdump -i $(TZS_CUTOFF_FLAG) '$(wd)/'$$(expr $@ : '\(.*\).zd') \ + >$@ + +TZS_NEW_DEPS = tzdata.zi zdump zic +$(TZS_NEW): $(TZS_NEW_DEPS) + rm -fr tzs$(TZS_YEAR).dir + mkdir tzs$(TZS_YEAR).dir + $(zic) -d tzs$(TZS_YEAR).dir tzdata.zi + $(AWK) '/^L/{print "Link\t" $$2 "\t" $$3}' \ + tzdata.zi | LC_ALL=C sort >$@.out + wd=`pwd` && \ + x=`$(AWK) '/^Z/{print "tzs$(TZS_YEAR).dir/" $$2 ".zd"}' \ + tzdata.zi \ + | LC_ALL=C sort -t . -k 2,2` && \ + set x $$x && \ + shift && \ + ZDS=$$* && \ + $(MAKE) wd="$$wd" TZS_CUTOFF_FLAG="$(TZS_CUTOFF_FLAG)" \ + ZDS="$$ZDS" $$ZDS && \ + sed 's,^TZ=".*\.dir/,TZ=",' $$ZDS >>$@.out + rm -fr tzs$(TZS_YEAR).dir + mv $@.out $@ + +# If $(TZS) exists but 'make check_tzs' fails, a maintainer should inspect the +# failed output and fix the inconsistency, perhaps by running 'make force_tzs'. +$(TZS): + touch $@ + +force_tzs: $(TZS_NEW) + cp $(TZS_NEW) $(TZS) + +libtz.a: $(LIBOBJS) + rm -f $@ + $(AR) -rc $@ $(LIBOBJS) + $(RANLIB) $@ + +date: $(DATEOBJS) + $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $(DATEOBJS) $(LDLIBS) + +tzselect: tzselect.ksh version + VERSION=`cat version` && sed \ + -e 's|#!/bin/bash|#!$(KSHELL)|g' \ + -e 's|AWK=[^}]*|AWK=$(AWK)|g' \ + -e 's|\(PKGVERSION\)=.*|\1='\''($(PACKAGE)) '\''|' \ + -e 's|\(REPORT_BUGS_TO\)=.*|\1=$(BUGEMAIL)|' \ + -e 's|TZDIR=[^}]*|TZDIR=$(TZDIR)|' \ + -e 's|\(TZVERSION\)=.*|\1='"$$VERSION"'|' \ + <$@.ksh >$@.out + chmod +x $@.out + mv $@.out $@ + +check: check_character_set check_white_space check_links \ + check_name_lengths check_sorted \ + check_tables check_web check_zishrink check_tzs + +check_character_set: $(ENCHILADA) + test ! '$(UTF8_LOCALE)' || \ + ! printf 'A\304\200B\n' | \ + LC_ALL='$(UTF8_LOCALE)' grep -q '^A.B$$' >/dev/null 2>&1 || { \ + LC_ALL='$(UTF8_LOCALE)' && export LC_ALL && \ + sharp='#' && \ + ! grep -Env $(SAFE_LINE) $(MANS) date.1 $(MANTXTS) \ + $(MISC) $(SOURCES) $(WEB_PAGES) \ + CONTRIBUTING LICENSE README \ + version tzdata.zi && \ + ! grep -Env $(SAFE_LINE)'|^UNUSUAL_OK_'$(OK_CHAR)'*$$' \ + Makefile && \ + ! grep -Env $(SAFE_SHARP_LINE) $(TDATA_TO_CHECK) backzone \ + leapseconds yearistype.sh zone.tab && \ + ! grep -Env $(OK_LINE) $(ENCHILADA); \ + } + touch $@ + +check_white_space: $(ENCHILADA) + patfmt=' \t|[\f\r\v]' && pat=`printf "$$patfmt\\n"` && \ + ! grep -En "$$pat" \ + $$(ls $(ENCHILADA) | grep -Fvx leap-seconds.list) + ! grep -n '[[:space:]]$$' \ + $$(ls $(ENCHILADA) | grep -Fvx leap-seconds.list) + touch $@ + +PRECEDES_FILE_NAME = ^(Zone|Link[[:space:]]+[^[:space:]]+)[[:space:]]+ +FILE_NAME_COMPONENT_TOO_LONG = \ + $(PRECEDES_FILE_NAME)[^[:space:]]*[^/[:space:]]{15} + +check_name_lengths: $(TDATA_TO_CHECK) backzone + ! grep -En '$(FILE_NAME_COMPONENT_TOO_LONG)' \ + $(TDATA_TO_CHECK) backzone + touch $@ + +CHECK_CC_LIST = { n = split($$1,a,/,/); for (i=2; i<=n; i++) print a[1], a[i]; } + +check_sorted: backward backzone iso3166.tab zone.tab zone1970.tab + $(AWK) '/^Link/ {print $$3}' backward | LC_ALL=C sort -cu + $(AWK) '/^Zone/ {print $$2}' backzone | LC_ALL=C sort -cu + touch $@ + +check_links: checklinks.awk $(TDATA_TO_CHECK) tzdata.zi + $(AWK) -f checklinks.awk $(TDATA_TO_CHECK) + $(AWK) -f checklinks.awk tzdata.zi + touch $@ + +check_tables: checktab.awk $(PRIMARY_YDATA) $(ZONETABLES) + for tab in $(ZONETABLES); do \ + $(AWK) -f checktab.awk -v zone_table=$$tab $(PRIMARY_YDATA) \ + || exit; \ + done + touch $@ + +check_tzs: $(TZS) $(TZS_NEW) + if test -s $(TZS); then \ + diff -u $(TZS) $(TZS_NEW); \ + else \ + cp $(TZS_NEW) $(TZS); \ + fi + touch $@ + +check_web: $(CHECK_WEB_PAGES) +check_theory.html: theory.html +check_tz-art.html: tz-art.html +check_tz-link.html: tz-link.html +check_theory.html check_tz-art.html check_tz-link.html: + $(CURL) -sS --url https://validator.w3.org/nu/ -F out=gnu \ + -F file=@$$(expr $@ : 'check_\(.*\)') -o $@.out && \ + test ! -s $@.out || { cat $@.out; exit 1; } + mv $@.out $@ +check_tz-how-to.html: tz-how-to.html + $(VALIDATE_ENV) $(VALIDATE) $(VALIDATE_FLAGS) tz-how-to.html + touch $@ + +# Check that zishrink.awk does not alter the data, and that ziguard.awk +# preserves main-format data. +check_zishrink: check_zishrink_posix check_zishrink_right +check_zishrink_posix check_zishrink_right: \ + zic leapseconds $(PACKRATDATA) $(TDATA) $(DATAFORM).zi tzdata.zi + rm -fr $@.dir $@-t.dir $@-shrunk.dir + mkdir $@.dir $@-t.dir $@-shrunk.dir + case $@ in \ + *_right) leap='-L leapseconds';; \ + *) leap=;; \ + esac && \ + $(ZIC) $$leap -d $@.dir $(DATAFORM).zi && \ + $(ZIC) $$leap -d $@-shrunk.dir tzdata.zi && \ + case $(DATAFORM) in \ + main) \ + $(ZIC) $$leap -d $@-t.dir $(TDATA) && \ + $(AWK) '/^Rule/' $(TDATA) | \ + $(ZIC) $$leap -d $@-t.dir - $(PACKRATDATA) && \ + diff -r $@.dir $@-t.dir;; \ + esac + diff -r $@.dir $@-shrunk.dir + rm -fr $@.dir $@-t.dir $@-shrunk.dir + touch $@ + +clean_misc: + rm -fr check_*.dir + rm -f *.o *.out $(TIME_T_ALTERNATIVES) \ + check_* core typecheck_* \ + date tzselect version.h zdump zic yearistype libtz.a +clean: clean_misc + rm -fr *.dir tzdb-*/ + rm -f *.zi $(TZS_NEW) + +maintainer-clean: clean + @echo 'This command is intended for maintainers to use; it' + @echo 'deletes files that may need special tools to rebuild.' + rm -f leapseconds version $(MANTXTS) $(TZS) *.asc *.tar.* + +names: + @echo $(ENCHILADA) + +public: check check_public $(CHECK_TIME_T_ALTERNATIVES) \ + tarballs signatures + +date.1.txt: date.1 +newctime.3.txt: newctime.3 +newstrftime.3.txt: newstrftime.3 +newtzset.3.txt: newtzset.3 +time2posix.3.txt: time2posix.3 +tzfile.5.txt: tzfile.5 +tzselect.8.txt: tzselect.8 +zdump.8.txt: zdump.8 +zic.8.txt: zic.8 + +$(MANTXTS): workman.sh + LC_ALL=C sh workman.sh `expr $@ : '\(.*\)\.txt$$'` >$@.out + mv $@.out $@ + +# Set the timestamps to those of the git repository, if available, +# and if the files have not changed since then. +# This uses GNU 'touch' syntax 'touch -d@N FILE', +# where N is the number of seconds since 1970. +# If git or GNU 'touch' is absent, don't bother to sync with git timestamps. +# Also, set the timestamp of each prebuilt file like 'leapseconds' +# to be the maximum of the files it depends on. +set-timestamps.out: $(EIGHT_YARDS) + rm -f $@ + if (type git) >/dev/null 2>&1 && \ + files=`git ls-files $(EIGHT_YARDS)` && \ + touch -md @1 test.out; then \ + rm -f test.out && \ + for file in $$files; do \ + if git diff --quiet $$file; then \ + time=`git log -1 --format='tformat:%ct' $$file` && \ + touch -cmd @$$time $$file; \ + else \ + echo >&2 "$$file: warning: does not match repository"; \ + fi || exit; \ + done; \ + fi + touch -cmr `ls -t $(LEAP_DEPS) | sed 1q` leapseconds + for file in `ls $(MANTXTS) | sed 's/\.txt$$//'`; do \ + touch -cmr `ls -t $$file workman.sh | sed 1q` $$file.txt || \ + exit; \ + done + touch -cmr `ls -t $(TZDATA_ZI_DEPS) | sed 1q` tzdata.zi + touch -cmr `ls -t $(VERSION_DEPS) | sed 1q` version + touch $@ +set-tzs-timestamp.out: $(TZS) + touch -cmr `ls -t $(TZS_DEPS) | sed 1q` $(TZS) + touch $@ + +# The zics below ensure that each data file can stand on its own. +# We also do an all-files run to catch links to links. + +check_public: $(VERSION_DEPS) + rm -fr public.dir + mkdir public.dir + ln $(VERSION_DEPS) public.dir + cd public.dir && $(MAKE) CFLAGS='$(GCC_DEBUG_FLAGS)' ALL + for i in $(TDATA_TO_CHECK) public.dir/tzdata.zi; do \ + public.dir/zic -v -d public.dir/zoneinfo $$i 2>&1 || exit; \ + done + public.dir/zic -v -d public.dir/zoneinfo-all $(TDATA_TO_CHECK) + rm -fr public.dir + touch $@ + +# Check that the code works under various alternative +# implementations of time_t. +check_time_t_alternatives: $(TIME_T_ALTERNATIVES) +$(TIME_T_ALTERNATIVES_TAIL): $(TIME_T_ALTERNATIVES_HEAD) +$(TIME_T_ALTERNATIVES): $(VERSION_DEPS) + rm -fr $@.dir + mkdir $@.dir + ln $(VERSION_DEPS) $@.dir + case $@ in \ + int32_t) range=-2147483648,2147483648;; \ + u*) range=0,4294967296;; \ + *) range=-4294967296,4294967296;; \ + esac && \ + wd=`pwd` && \ + zones=`$(AWK) '/^[^#]/ { print $$3 }' <zone1970.tab` && \ + if test $@ = $(TIME_T_ALTERNATIVES_HEAD); then \ + range_target=; \ + else \ + range_target=to$$range.tzs; \ + fi && \ + (cd $@.dir && \ + $(MAKE) TOPDIR="$$wd/$@.dir" \ + CFLAGS='$(CFLAGS) -Dtime_tz='"'$@'" \ + REDO='$(REDO)' \ + D=$$wd/$@.dir \ + TZS_YEAR="$$range" TZS_CUTOFF_FLAG="-t $$range" \ + install $$range_target) && \ + test $@ = $(TIME_T_ALTERNATIVES_HEAD) || { \ + (cd $(TIME_T_ALTERNATIVES_HEAD).dir && \ + $(MAKE) TOPDIR="$$wd/$@.dir" \ + TZS_YEAR="$$range" TZS_CUTOFF_FLAG="-t $$range" \ + D=$$wd/$@.dir \ + to$$range.tzs) && \ + diff -u $(TIME_T_ALTERNATIVES_HEAD).dir/to$$range.tzs \ + $@.dir/to$$range.tzs && \ + if diff -q Makefile Makefile 2>/dev/null; then \ + quiet_option='-q'; \ + else \ + quiet_option=''; \ + fi && \ + diff $$quiet_option -r $(TIME_T_ALTERNATIVES_HEAD).dir/etc \ + $@.dir/etc && \ + diff $$quiet_option -r \ + $(TIME_T_ALTERNATIVES_HEAD).dir/usr/share \ + $@.dir/usr/share; \ + } + touch $@ + +TRADITIONAL_ASC = \ + tzcode$(VERSION).tar.gz.asc \ + tzdata$(VERSION).tar.gz.asc +REARGUARD_ASC = \ + tzdata$(VERSION)-rearguard.tar.gz.asc +ALL_ASC = $(TRADITIONAL_ASC) $(REARGUARD_ASC) \ + tzdb-$(VERSION).tar.lz.asc + +tarballs rearguard_tarballs traditional_tarballs \ +signatures rearguard_signatures traditional_signatures: \ + version set-timestamps.out rearguard.zi + VERSION=`cat version` && \ + $(MAKE) VERSION="$$VERSION" $@_version + +# These *_version rules are intended for use if VERSION is set by some +# other means. Ordinarily these rules are used only by the above +# non-_version rules, which set VERSION on the 'make' command line. +tarballs_version: traditional_tarballs_version rearguard_tarballs_version \ + tzdb-$(VERSION).tar.lz +rearguard_tarballs_version: \ + tzdata$(VERSION)-rearguard.tar.gz +traditional_tarballs_version: \ + tzcode$(VERSION).tar.gz tzdata$(VERSION).tar.gz +signatures_version: $(ALL_ASC) +rearguard_signatures_version: $(REARGUARD_ASC) +traditional_signatures_version: $(TRADITIONAL_ASC) + +tzcode$(VERSION).tar.gz: set-timestamps.out + LC_ALL=C && export LC_ALL && \ + tar $(TARFLAGS) -cf - \ + $(COMMON) $(DOCS) $(SOURCES) | \ + gzip $(GZIPFLAGS) >$@.out + mv $@.out $@ + +tzdata$(VERSION).tar.gz: set-timestamps.out + LC_ALL=C && export LC_ALL && \ + tar $(TARFLAGS) -cf - $(COMMON) $(DATA) $(MISC) | \ + gzip $(GZIPFLAGS) >$@.out + mv $@.out $@ + +tzdata$(VERSION)-rearguard.tar.gz: rearguard.zi set-timestamps.out + rm -fr tzdata$(VERSION)-rearguard.dir + mkdir tzdata$(VERSION)-rearguard.dir + ln $(COMMON) $(DATA) $(MISC) tzdata$(VERSION)-rearguard.dir + cd tzdata$(VERSION)-rearguard.dir && \ + rm -f $(TDATA) $(PACKRATDATA) version + for f in $(TDATA) $(PACKRATDATA); do \ + rearf=tzdata$(VERSION)-rearguard.dir/$$f; \ + $(AWK) -v DATAFORM=rearguard -f ziguard.awk $$f >$$rearf && \ + touch -cmr `ls -t ziguard.awk $$f` $$rearf || exit; \ + done + sed '1s/$$/-rearguard/' \ + <version >tzdata$(VERSION)-rearguard.dir/version + touch -cmr version tzdata$(VERSION)-rearguard.dir/version + LC_ALL=C && export LC_ALL && \ + (cd tzdata$(VERSION)-rearguard.dir && \ + tar $(TARFLAGS) -cf - $(COMMON) $(DATA) $(MISC) | \ + gzip $(GZIPFLAGS)) >$@.out + mv $@.out $@ + +tzdb-$(VERSION).tar.lz: set-timestamps.out set-tzs-timestamp.out + rm -fr tzdb-$(VERSION) + mkdir tzdb-$(VERSION) + ln $(ENCHILADA) tzdb-$(VERSION) + touch -cmr `ls -t tzdb-$(VERSION)/* | sed 1q` tzdb-$(VERSION) + LC_ALL=C && export LC_ALL && \ + tar $(TARFLAGS) -cf - tzdb-$(VERSION) | lzip -9 >$@.out + mv $@.out $@ + +tzcode$(VERSION).tar.gz.asc: tzcode$(VERSION).tar.gz +tzdata$(VERSION).tar.gz.asc: tzdata$(VERSION).tar.gz +tzdata$(VERSION)-rearguard.tar.gz.asc: tzdata$(VERSION)-rearguard.tar.gz +tzdb-$(VERSION).tar.lz.asc: tzdb-$(VERSION).tar.lz +$(ALL_ASC): + $(GPG) --armor --detach-sign $? + +TYPECHECK_CFLAGS = $(CFLAGS) -DTYPECHECK -D__time_t_defined -D_TIME_T +typecheck: typecheck_long_long typecheck_unsigned +typecheck_long_long typecheck_unsigned: $(VERSION_DEPS) + rm -fr $@.dir + mkdir $@.dir + ln $(VERSION_DEPS) $@.dir + cd $@.dir && \ + case $@ in \ + *_long_long) i="long long";; \ + *_unsigned ) i="unsigned" ;; \ + esac && \ + typecheck_cflags='' && \ + $(MAKE) \ + CFLAGS="$(TYPECHECK_CFLAGS) \"-Dtime_t=$$i\"" \ + TOPDIR="`pwd`" \ + install + $@.dir/zdump -i -c 1970,1971 Europe/Rome + touch $@ + +zonenames: tzdata.zi + @$(AWK) '/^Z/ { print $$2 } /^L/ { print $$3 }' tzdata.zi + +asctime.o: private.h tzfile.h +date.o: private.h +difftime.o: private.h +localtime.o: private.h tzfile.h +strftime.o: private.h tzfile.h +zdump.o: version.h +zic.o: private.h tzfile.h version.h + +.KEEP_STATE: + +.PHONY: ALL INSTALL all +.PHONY: check check_time_t_alternatives +.PHONY: check_web check_zishrink +.PHONY: clean clean_misc dummy.zd force_tzs +.PHONY: install install_data maintainer-clean names +.PHONY: posix_only posix_packrat posix_right public +.PHONY: rearguard_signatures rearguard_signatures_version +.PHONY: rearguard_tarballs rearguard_tarballs_version +.PHONY: right_only right_posix signatures signatures_version +.PHONY: tarballs tarballs_version +.PHONY: traditional_signatures traditional_signatures_version +.PHONY: traditional_tarballs traditional_tarballs_version +.PHONY: typecheck +.PHONY: zonenames zones +.PHONY: $(ZDS) diff --git a/antarctica b/tz/antarctica index 2059983..2059983 100644 --- a/antarctica +++ b/tz/antarctica diff --git a/australasia b/tz/australasia index 0544e58..0544e58 100644 --- a/australasia +++ b/tz/australasia diff --git a/checklinks.awk b/tz/checklinks.awk index f309010..f309010 100644 --- a/checklinks.awk +++ b/tz/checklinks.awk diff --git a/checktab.awk b/tz/checktab.awk index ec145b5..ec145b5 100644 --- a/checktab.awk +++ b/tz/checktab.awk diff --git a/difftime.c b/tz/difftime.c index 7b96927..7b96927 100644 --- a/difftime.c +++ b/tz/difftime.c diff --git a/iso3166.tab b/tz/iso3166.tab index a4ff61a..a4ff61a 100644 --- a/iso3166.tab +++ b/tz/iso3166.tab diff --git a/leap-seconds.list b/tz/leap-seconds.list index fd3d192..fd3d192 100644 --- a/leap-seconds.list +++ b/tz/leap-seconds.list diff --git a/leapseconds.awk b/tz/leapseconds.awk index 242e9d6..242e9d6 100644 --- a/leapseconds.awk +++ b/tz/leapseconds.awk diff --git a/localtime.c b/tz/localtime.c index 033e88f..033e88f 100644 --- a/localtime.c +++ b/tz/localtime.c diff --git a/newctime.3 b/tz/newctime.3 index 565e89a..565e89a 100644 --- a/newctime.3 +++ b/tz/newctime.3 diff --git a/newstrftime.3 b/tz/newstrftime.3 index eee503e..eee503e 100644 --- a/newstrftime.3 +++ b/tz/newstrftime.3 diff --git a/newtzset.3 b/tz/newtzset.3 index 4959851..4959851 100644 --- a/newtzset.3 +++ b/tz/newtzset.3 diff --git a/northamerica b/tz/northamerica index 9e3577b..9e3577b 100644 --- a/northamerica +++ b/tz/northamerica diff --git a/pacificnew b/tz/pacificnew index 8403219..8403219 100644 --- a/pacificnew +++ b/tz/pacificnew diff --git a/southamerica b/tz/southamerica index 397a2cc..397a2cc 100644 --- a/southamerica +++ b/tz/southamerica diff --git a/strftime.c b/tz/strftime.c index ac26f4b..ac26f4b 100644 --- a/strftime.c +++ b/tz/strftime.c diff --git a/theory.html b/tz/theory.html index e0b3233..e0b3233 100644 --- a/theory.html +++ b/tz/theory.html diff --git a/time2posix.3 b/tz/time2posix.3 index e4b8e81..e4b8e81 100644 --- a/time2posix.3 +++ b/tz/time2posix.3 diff --git a/tz-art.html b/tz/tz-art.html index 8c57174..8c57174 100644 --- a/tz-art.html +++ b/tz/tz-art.html diff --git a/tz-how-to.html b/tz/tz-how-to.html index 2e4842d..2e4842d 100644 --- a/tz-how-to.html +++ b/tz/tz-how-to.html diff --git a/tz-link.html b/tz/tz-link.html index 85fc001..85fc001 100644 --- a/tz-link.html +++ b/tz/tz-link.html diff --git a/tzselect.8 b/tz/tzselect.8 index 51f751c..51f751c 100644 --- a/tzselect.8 +++ b/tz/tzselect.8 diff --git a/tzselect.ksh b/tz/tzselect.ksh index 18fce27..18fce27 100644 --- a/tzselect.ksh +++ b/tz/tzselect.ksh diff --git a/workman.sh b/tz/workman.sh index 8fb18a4..8fb18a4 100644 --- a/workman.sh +++ b/tz/workman.sh diff --git a/yearistype.sh b/tz/yearistype.sh index d674175..d674175 100644 --- a/yearistype.sh +++ b/tz/yearistype.sh diff --git a/ziguard.awk b/tz/ziguard.awk index e8ef49e..e8ef49e 100644 --- a/ziguard.awk +++ b/tz/ziguard.awk diff --git a/zishrink.awk b/tz/zishrink.awk index 4e187ac..4e187ac 100644 --- a/zishrink.awk +++ b/tz/zishrink.awk diff --git a/zone1970.tab b/tz/zone1970.tab index 822ffa1..822ffa1 100644 --- a/zone1970.tab +++ b/tz/zone1970.tab diff --git a/zoneinfo2tdf.pl b/tz/zoneinfo2tdf.pl index e07b00c..e07b00c 100755 --- a/zoneinfo2tdf.pl +++ b/tz/zoneinfo2tdf.pl |