[Python-modules-commits] [python-udatetime] 01/01: New upstream version 0.0.9

Ilias Tsitsimpis iliastsi-guest at moszumanska.debian.org
Sun Sep 11 20:58:30 UTC 2016


This is an automated email from the git hooks/post-receive script.

iliastsi-guest pushed a commit to branch upstream
in repository python-udatetime.

commit edceebc5a4163cae34a906944e1aa838cbe69f37
Author: Ilias Tsitsimpis <i.tsitsimpis at gmail.com>
Date:   Sun Sep 11 13:41:52 2016 +0300

    New upstream version 0.0.9
---
 MANIFEST.in                             |    1 +
 PKG-INFO                                |   23 +
 README.md                               |  285 +++++++++
 requirements.txt                        |    0
 scripts/bench_udatetime.py              |  135 ++++
 setup.cfg                               |    5 +
 setup.py                                |   69 +++
 src/rfc3339.c                           | 1034 +++++++++++++++++++++++++++++++
 test/test_udatetime.py                  |  189 ++++++
 udatetime.egg-info/PKG-INFO             |   23 +
 udatetime.egg-info/SOURCES.txt          |   15 +
 udatetime.egg-info/dependency_links.txt |    1 +
 udatetime.egg-info/not-zip-safe         |    1 +
 udatetime.egg-info/top_level.txt        |    2 +
 udatetime/__init__.py                   |   35 ++
 udatetime/_pure.py                      |  205 ++++++
 version.txt                             |    1 +
 17 files changed, 2024 insertions(+)

diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..fa30486
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1 @@
+include *.txt *.cfg *.md *.py
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..38d2455
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,23 @@
+Metadata-Version: 1.1
+Name: udatetime
+Version: 0.0.9
+Summary: Fast RFC3339 compliant date-time library
+Home-page: https://github.com/freach/udatetime
+Author: Simon Pirschel
+Author-email: simon at aboutsimon.com
+License: Apache 2.0
+Description: # udatetime: Fast RFC3339 compliant date-time library
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Natural Language :: English
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: MacOS
+Classifier: Programming Language :: C
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c327ec5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,285 @@
+# udatetime: Fast RFC3339 compliant date-time library
+
+Handling date-times is a painful act because of the sheer endless amount
+of formats used by people. Luckily there are a couple of specified standards
+out there like [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601). But even
+ISO 8601 leaves to many options on how to define date and time. That's why I
+encourage using the [RFC3339](https://www.ietf.org/rfc/rfc3339.txt) specified
+date-time format.
+
+`udatetime` offers on average 76% faster `datetime` object instantiation,
+serialization and deserialization of RFC3339 date-time strings. `udatetime` is
+using Python's [datetime class](https://docs.python.org/2/library/datetime.html)
+under the hood and code already using `datetime` should be able to easily
+switch to `udatetime`. All `datetime` objects created by `udatetime` are timezone
+aware.
+
+|          | Support            | Performance optimized | Implementation |
+| -------- |:------------------:|:---------------------:| -------------- |
+| Python 2 | :heavy_check_mark: |  :heavy_check_mark:   | C              |
+| Python 3 | :heavy_check_mark: |  :heavy_check_mark:   | C              |
+| PyPy     | :heavy_check_mark: |  :heavy_check_mark:   | Pure Python    |
+
+```python
+>>> udatetime.from_string("2016-07-15T12:33:20.123000+02:00")
+datetime.datetime(2016, 7, 15, 12, 33, 20, 123000, tzinfo=+02:00)
+
+>>> dt = udatetime.from_string("2016-07-15T12:33:20.123000+02:00")
+>>> udatetime.to_string(dt)
+'2016-07-15T12:33:20.123000+02:00'
+
+>>> udatetime.now()
+datetime.datetime(2016, 7, 29, 10, 15, 24, 472467, tzinfo=+02:00)
+
+>>> udatetime.utcnow()
+datetime.datetime(2016, 7, 29, 8, 15, 36, 184762, tzinfo=+00:00)
+
+>>> udatetime.now_to_string()
+'2016-07-29T10:15:46.641233+02:00'
+
+>>> udatetime.utcnow_to_string()
+'2016-07-29T08:15:56.129798+00:00'
+
+>>> udatetime.to_string(udatetime.utcnow() - timedelta(hours=6))
+'2016-07-29T02:16:05.770358+00:00'
+
+>>> udatetime.fromtimestamp(time.time())
+datetime.datetime(2016, 7, 30, 17, 45, 1, 536586, tzinfo=+02:00)
+
+>>> udatetime.utcfromtimestamp(time.time())
+datetime.datetime(2016, 8, 1, 10, 14, 53, tzinfo=+00:00)
+```
+
+## Installation
+
+Currently only **POSIX** compliant systems are supported.
+Working on cross-platform support.
+
+```
+$ pip install udatetime
+```
+
+You might need to install the header files of your Python installation and
+some essential tools to execute the build like a C compiler.
+
+**Python 2**
+
+```
+$ sudo apt-get install python-dev build-essential
+```
+
+or
+
+```
+$ sudo yum install python-devel gcc
+```
+
+**Python 3**
+
+```
+$ sudo apt-get install python3-dev build-essential
+```
+
+or
+
+```
+$ sudo yum install python3-devel gcc
+```
+
+## Benchmark
+
+The benchmarks compare the performance of equivalent code of `datetime` and
+`udatetime`. The benchmark measures the time needed for 1 million executions
+and takes the `min` of 3 repeats. You can run the benchmark yourself and see
+the results on your machine by executing the `bench_udatetime.py` script.
+
+![Benchmark interpreter summary](/extras/benchmark_interpreter_summary.png?raw=true "datetime vs. udatetime summary")
+
+### Python 2.7
+
+```
+$ python scripts/bench_udatetime.py
+Executing benchmarks ...
+
+============ benchmark_parse
+datetime_strptime 10.6306970119
+udatetime_parse 1.109801054
+udatetime is 9.6 times faster
+
+============ benchmark_format
+datetime_strftime 2.08363199234
+udatetime_format 0.654432058334
+udatetime is 3.2 times faster
+
+============ benchmark_utcnow
+datetime_utcnow 0.485884904861
+udatetime_utcnow 0.237742185593
+udatetime is 2.0 times faster
+
+============ benchmark_now
+datetime_now 1.37059998512
+udatetime_now 0.235424041748
+udatetime is 5.8 times faster
+
+============ benchmark_utcnow_to_string
+datetime_utcnow_to_string 2.56599593163
+udatetime_utcnow_to_string 0.685483932495
+udatetime is 3.7 times faster
+
+============ benchmark_now_to_string
+datetime_now_to_string 3.68989396095
+udatetime_now_to_string 0.687911987305
+udatetime is 5.4 times faster
+
+============ benchmark_fromtimestamp
+datetime_fromtimestamp 1.38640713692
+udatetime_fromtimestamp 0.287910938263
+udatetime is 4.8 times faster
+
+============ benchmark_utcfromtimestamp
+datetime_utcfromtimestamp 0.533131837845
+udatetime_utcfromtimestamp 0.279694080353
+udatetime is 1.9 times faster
+```
+
+### Python 3.5
+
+```
+$ python scripts/bench_udatetime.py
+Executing benchmarks ...
+
+============ benchmark_parse
+datetime_strptime 9.90670353400003
+udatetime_parse 1.1668808249999074
+udatetime is 8.5 times faster
+
+============ benchmark_format
+datetime_strftime 3.0286041580000074
+udatetime_format 0.7153575119999687
+udatetime is 4.2 times faster
+
+============ benchmark_utcnow
+datetime_utcnow 0.5638177430000724
+udatetime_utcnow 0.2548112540000602
+udatetime is 2.2 times faster
+
+============ benchmark_now
+datetime_now 1.457822759999999
+udatetime_now 0.26195338699994863
+udatetime is 5.6 times faster
+
+============ benchmark_utcnow_to_string
+datetime_utcnow_to_string 3.5347913849999486
+udatetime_utcnow_to_string 0.750341161999927
+udatetime is 4.7 times faster
+
+============ benchmark_now_to_string
+datetime_now_to_string 4.854975383999999
+udatetime_now_to_string 0.7411948169999505
+udatetime is 6.6 times faster
+
+============ benchmark_fromtimestamp
+datetime_fromtimestamp 1.4233373309999706
+udatetime_fromtimestamp 0.31758270299997093
+udatetime is 4.5 times faster
+
+============ benchmark_utcfromtimestamp
+datetime_utcfromtimestamp 0.5633522409999614
+udatetime_utcfromtimestamp 0.305099536000057
+udatetime is 1.8 times faster
+```
+
+### PyPy 5.3.1
+
+```
+$ python scripts/bench_udatetime.py
+Executing benchmarks ...
+
+============ benchmark_parse
+datetime_strptime 2.31050491333
+udatetime_parse 0.838973045349
+udatetime is 2.8 times faster
+
+============ benchmark_format
+datetime_strftime 0.957178115845
+udatetime_format 0.162060976028
+udatetime is 5.9 times faster
+
+============ benchmark_utcnow
+datetime_utcnow 0.149839878082
+udatetime_utcnow 0.149217844009
+udatetime is as fast as datetime
+
+============ benchmark_now
+datetime_now 0.967023134232
+udatetime_now 0.15003991127
+udatetime is 6.4 times faster
+
+============ benchmark_utcnow_to_string
+datetime_utcnow_to_string 1.24988698959
+udatetime_utcnow_to_string 0.632546901703
+udatetime is 2.0 times faster
+
+============ benchmark_now_to_string
+datetime_now_to_string 2.13041496277
+udatetime_now_to_string 0.607964038849
+udatetime is 3.5 times faster
+
+============ benchmark_fromtimestamp
+datetime_fromtimestamp 0.903736114502
+udatetime_fromtimestamp 0.0907990932465
+udatetime is 10.0 times faster
+
+============ benchmark_utcfromtimestamp
+datetime_utcfromtimestamp 0.0890419483185
+udatetime_utcfromtimestamp 0.0907027721405
+udatetime is as fast as datetime
+```
+
+## Why RFC3339
+
+The RFC3339 specification has the following advantages:
+
+- Defined date, time, timezone, date-time format
+- 4 digit year
+- Fractional seconds
+- Human readable
+- No redundant information like weekday name
+- Simple specification, easily machine readable
+
+### RFC3339 format specification
+
+```
+date-fullyear   = 4DIGIT
+date-month      = 2DIGIT  ; 01-12
+date-mday       = 2DIGIT  ; 01-28, 01-29, 01-30, 01-31 based on
+                          ; month/year
+time-hour       = 2DIGIT  ; 00-23
+time-minute     = 2DIGIT  ; 00-59
+time-second     = 2DIGIT  ; 00-58, 00-59, 00-60 based on leap second
+                          ; rules
+time-secfrac    = "." 1*DIGIT
+time-numoffset  = ("+" / "-") time-hour ":" time-minute
+time-offset     = "Z" / time-numoffset
+
+partial-time    = time-hour ":" time-minute ":" time-second [time-secfrac]
+
+full-date       = date-fullyear "-" date-month "-" date-mday
+full-time       = partial-time time-offset
+
+date-time       = full-date "T" full-time
+```
+
+`udatetime` specific format addons:
+
+- time-secfrac can be either 3DIGIT for milliseconds or 6DIGIT for microseconds
+- time-secfrac will be normalized to microseconds
+- time-offset is optional. Missing time-offset will be treated as UTC.
+- spaces will be eliminated
+
+## Why in C?
+
+The Python `datetime` library is flexible but painfully slow, when it comes to
+parsing and formating. High performance applications like web services or
+logging benefit from fast underlying libraries. Restricting the input format
+to one standard allows for optimization and results in speed improvements.
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..e69de29
diff --git a/scripts/bench_udatetime.py b/scripts/bench_udatetime.py
new file mode 100644
index 0000000..0ba7aab
--- /dev/null
+++ b/scripts/bench_udatetime.py
@@ -0,0 +1,135 @@
+from __future__ import print_function
+from datetime import datetime
+from time import time
+import udatetime
+
+RFC3339_DATE = '2016-07-18'
+RFC3339_TIME = '12:58:26.485897+02:00'
+RFC3339_DATE_TIME = RFC3339_DATE + 'T' + RFC3339_TIME
+RFC3339_DATE_TIME_DTLIB = RFC3339_DATE_TIME[:-6]
+DATE_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%f'
+DATETIME_OBJ = datetime.strptime(RFC3339_DATE_TIME_DTLIB, DATE_TIME_FORMAT)
+TIME = time()
+
+
+def benchmark_parse():
+    def datetime_strptime():
+        datetime.strptime(RFC3339_DATE_TIME_DTLIB, DATE_TIME_FORMAT)
+
+    def udatetime_parse():
+        udatetime.from_string(RFC3339_DATE_TIME)
+
+    return (datetime_strptime, udatetime_parse)
+
+
+def benchmark_format():
+    def datetime_strftime():
+        DATETIME_OBJ.strftime(DATE_TIME_FORMAT)
+
+    def udatetime_format():
+        udatetime.to_string(DATETIME_OBJ)
+
+    return (datetime_strftime, udatetime_format)
+
+
+def benchmark_utcnow():
+    def datetime_utcnow():
+        datetime.utcnow()
+
+    def udatetime_utcnow():
+        udatetime.utcnow()
+
+    return (datetime_utcnow, udatetime_utcnow)
+
+
+def benchmark_now():
+    def datetime_now():
+        datetime.now()
+
+    def udatetime_now():
+        udatetime.now()
+
+    return (datetime_now, udatetime_now)
+
+
+def benchmark_utcnow_to_string():
+    def datetime_utcnow_to_string():
+        datetime.utcnow().strftime(DATE_TIME_FORMAT)
+
+    def udatetime_utcnow_to_string():
+        udatetime.utcnow_to_string()
+
+    return (datetime_utcnow_to_string, udatetime_utcnow_to_string)
+
+
+def benchmark_now_to_string():
+    def datetime_now_to_string():
+        datetime.now().strftime(DATE_TIME_FORMAT)
+
+    def udatetime_now_to_string():
+        udatetime.now_to_string()
+
+    return (datetime_now_to_string, udatetime_now_to_string)
+
+
+def benchmark_fromtimestamp():
+    def datetime_fromtimestamp():
+        datetime.fromtimestamp(TIME)
+
+    def udatetime_fromtimestamp():
+        udatetime.fromtimestamp(TIME)
+
+    return (datetime_fromtimestamp, udatetime_fromtimestamp)
+
+
+def benchmark_utcfromtimestamp():
+    def datetime_utcfromtimestamp():
+        datetime.utcfromtimestamp(TIME)
+
+    def udatetime_utcfromtimestamp():
+        udatetime.utcfromtimestamp(TIME)
+
+    return (datetime_utcfromtimestamp, udatetime_utcfromtimestamp)
+
+if __name__ == '__main__':
+    import timeit
+
+    benchmarks = [
+        benchmark_parse,
+        benchmark_format,
+
+        benchmark_utcnow,
+        benchmark_now,
+
+        benchmark_utcnow_to_string,
+        benchmark_now_to_string,
+
+        benchmark_fromtimestamp,
+        benchmark_utcfromtimestamp,
+    ]
+
+    print('Executing benchmarks ...')
+
+    for k in benchmarks:
+        print('\n============ %s' % k.__name__)
+        mins = []
+
+        for func in k():
+            times =\
+                timeit.repeat('func()', setup='from __main__ import func')
+            t = min(times)
+            mins.append(t)
+
+            print(func.__name__, t)
+
+        win = False
+        if mins[0] > mins[1]:
+            win = True
+
+        mins = sorted(mins)
+        diff = mins[1] / mins[0]
+
+        if win:
+            print('udatetime is %.01f times faster' % diff)
+        else:
+            print('udatetime is %.01f times slower' % diff)
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..861a9f5
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..97ddf04
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,69 @@
+from setuptools import setup, find_packages, Extension
+import os
+import sys
+
+try:
+    import __pypy__
+except ImportError:
+    __pypy__ = None
+
+__version__ = None
+here = os.path.abspath(os.path.dirname(__file__))
+name = 'udatetime'
+
+with open('%s/requirements.txt' % here) as f:
+    requires = f.readlines()
+
+with open('%s/version.txt' % here) as f:
+    __version__ = f.readline().strip()
+
+with open('%s/README.md' % here) as f:
+    readme = f.readline().strip()
+
+macros = []
+
+if __pypy__ is not None:
+    macros.append(('_PYPY', '1'))
+elif sys.version_info.major == 2:
+    macros.append(('_PYTHON2', '1'))
+elif sys.version_info.major == 3:
+    macros.append(('_PYTHON3', '1'))
+
+rfc3339 = Extension(
+    'rfc3339',
+    ['./src/rfc3339.c'],
+    define_macros=macros,
+    extra_compile_args=['-Ofast', '-std=c99']
+)
+
+setup(
+    name=name,
+    version=__version__,
+    description='Fast RFC3339 compliant date-time library',
+    long_description=readme,
+    license='Apache 2.0',
+    classifiers=[
+        'Development Status :: 4 - Beta',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: Apache Software License',
+        'Natural Language :: English',
+        'Operating System :: POSIX',
+        'Operating System :: MacOS',
+        'Programming Language :: C',
+        'Programming Language :: Python',
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3.5',
+        'Programming Language :: Python :: Implementation :: CPython',
+        'Programming Language :: Python :: Implementation :: PyPy',
+    ],
+    author='Simon Pirschel',
+    author_email='simon at aboutsimon.com',
+    url='https://github.com/freach/udatetime',
+    packages=find_packages(),
+    include_package_data=True,
+    zip_safe=False,
+    install_requires=requires,
+    ext_modules=[rfc3339],
+    scripts=['scripts/bench_udatetime.py'],
+)
diff --git a/src/rfc3339.c b/src/rfc3339.c
new file mode 100644
index 0000000..722cc4b
--- /dev/null
+++ b/src/rfc3339.c
@@ -0,0 +1,1034 @@
+#define _GNU_SOURCE 1
+
+#if defined(_PYTHON2) || defined(_PYTHON3)
+#include <Python.h>
+#include <datetime.h>
+#include <structmember.h>
+#endif
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+
+#define RFC3339_VERSION "0.0.6"
+#define DAY_IN_SECS 86400
+#define HOUR_IN_SECS 3600
+#define MINUTE_IN_SECS 60
+#define HOUR_IN_MINS 60
+
+typedef struct {
+    unsigned int year;
+    unsigned int month;
+    unsigned int day;
+    unsigned int wday;
+    char ok;
+} date_struct;
+
+typedef struct {
+    unsigned int hour;
+    unsigned int minute;
+    unsigned int second;
+    unsigned int fraction;
+    int offset; // UTC offset in minutes
+    char ok;
+} time_struct;
+
+typedef struct {
+    date_struct date;
+    time_struct time;
+    char ok;
+} date_time_struct;
+
+static int local_utc_offset; // local's system offset to UTC, init later
+
+/*
+ * Remove space characters from source
+ */
+static void _strip_spaces(char *source) {
+    char *i = source;
+
+    while(*source != 0) {
+        *i = *source++;
+        if (*i != ' ') i++;
+    }
+
+    *i = 0;
+}
+
+/* Get the local time zone's offset to UTC
+ *
+ * Uses tm_gmtoff in tm struct, which requires a POSIX system.
+ * TODO: Cross platform compatibility
+ */
+static int _get_local_utc_offset(void) {
+    if (!local_utc_offset) {
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+        struct tm info = {0};
+        time_t n = time(NULL);
+        localtime_r(&n, &info);
+
+        // tm_gmtoff requires POSIX
+        local_utc_offset = (int)info.tm_gmtoff / HOUR_IN_MINS;
+#else
+        local_utc_offset = 0;
+#endif
+    }
+
+    return local_utc_offset;
+}
+
+/* Get current time using gettimeofday(), ftime() or time() depending on
+ * support.
+ */
+static double _gettime(void) {
+#if defined(HAVE_GETTIMEOFDAY)
+    // => Use gettimeofday() in usec
+    struct timeval t;
+#if defined(GETTIMEOFDAY_NO_TZ)
+    if (gettimeofday(&t) == 0)
+        return ((double)t.tv_sec) + ((double)t.tv_usec * 0.000001);
+#else
+    struct timezone *tz = NULL;
+    if (gettimeofday(&t, tz) == 0)
+        return ((double)t.tv_sec) + ((double)t.tv_usec * 0.000001);
+#endif
+#elif defined(HAVE_FTIME)
+    // => Use ftime() in msec
+    struct timeb t;
+    ftime(&t);
+    return ((double)t.time) + ((double)t.millitm * 0.001);
+#else
+    // Fallback to time() in sec
+    time_t t;
+    time(&t);
+    return (double)t;
+#endif
+
+    // -Wreturn-type
+    return 0.0;
+}
+
+/*
+ * Parse a RFC3339 full-date
+ * full-date = date-fullyear "-" date-month "-" date-mday
+ * Ex. 2007-08-31
+ *
+ * Characters after date-mday are being ignored, so you can pass a
+ * date-time string and parse out only the full-date part.
+ */
+static void _parse_date(char *date_string, date_struct *d) {
+    // operate on string copy
+    char* const tokens = strdup(date_string);
+
+    // remove spaces
+    _strip_spaces(tokens);
+
+    // invalidate date_struct
+    (*d).ok = 0;
+
+    if (strlen(tokens) < 10)
+        return;
+
+    int status = sscanf(
+        tokens, "%04d-%02d-%02d", &((*d).year), &((*d).month), &((*d).day)
+    );
+
+    // Validate parsed tokens
+    if (status != 3) return;
+    if ((*d).year < 1 || (*d).year > 9999) return;
+    if ((*d).month < 1 || (*d).month > 12) return;
+    if ((*d).day < 1 || (*d).day > 31) return;
+
+    unsigned int leap = ((*d).year % 4 == 0) &&\
+        ((*d).year % 100 || ((*d).year % 400 == 0));
+
+    // Validate max day based on month
+    switch((*d).month) {
+        case 1:
+            if ((*d).day > 31)
+                return;
+            break;
+        case 2:
+            if (leap > 0) {
+                if ((*d).day > 29)
+                    return;
+            } else {
+                if ((*d).day > 28)
+                    return;
+            }
+            break;
+        case 3:
+            if ((*d).day > 31)
+                return;
+            break;
+        case 4:
+            if ((*d).day > 30)
+                return;
+            break;
+        case 5:
+            if ((*d).day > 31)
+                return;
+            break;
+        case 6:
+            if ((*d).day > 30)
+                return;
+            break;
+        case 7:
+            if ((*d).day > 31)
+                return;
+            break;
+        case 8:
+            if ((*d).day > 31)
+                return;
+            break;
+        case 9:
+            if ((*d).day > 30)
+                return;
+            break;
+        case 10:
+            if ((*d).day > 31)
+                return;
+            break;
+        case 11:
+            if ((*d).day > 30)
+                return;
+            break;
+        case 12:
+            if ((*d).day > 31)
+                return;
+            break;
+    }
+
+    (*d).ok = 1;
+}
+
+/*
+ * Parse a RFC3339 partial-time or full-time
+ * partial-time = time-hour ":" time-minute ":" time-second [time-secfrac]
+ * full-time    = partial-time time-offset
+ * Ex. 16:47:31.123+00:00, 18:21:00.123, 18:21:00
+ *
+ * If time_string is partial-time timezone will be UTC.
+ * If time_string is date-time, full-date part will be ignored.
+ */
+static void _parse_time(char *time_string, time_struct *t) {
+    // operate on string copy
+    char *tokens = strdup(time_string);
+    char *token_ptr = tokens;
+
+    // remove spaces
+    _strip_spaces(tokens);
+
+    // invalidate time_struct
+    (*t).ok = 0;
+
+    // must be at least hh:mm:ss, no timezone implicates UTC
+    if (strlen(tokens) < 8)
+        goto cleanup;
+
+    // check if time_string is date-time string, for convenience reasons
+    if ((strlen(tokens) > 11) && ((*(tokens + 10 ) == 'T') || (*(tokens + 10 ) == 't'))) {
+        tokens += 11;
+    }
+
+    int status = sscanf(
+        tokens, "%02d:%02d:%02d", &((*t).hour), &((*t).minute), &((*t).second)
+    );
+
+    // Validate parsed tokens
+    if (status != 3) goto cleanup;
+    if ((*t).hour < 0 || (*t).hour > 23) goto cleanup;
+    if ((*t).minute < 0 || (*t).minute > 59) goto cleanup;
+    if ((*t).second < 0 || (*t).second > 59) goto cleanup;
+
+    // dealt with hh:mm:ss
+    if (strlen(tokens) == 8) {
+        (*t).offset = 0;
+        (*t).ok = 1;
+        goto cleanup;
+    } else {
+        tokens += 8;
+    }
+
+    // check for fractions
+    if (*tokens == '.') {
+        tokens++;
+        char fractions[6] = {0};
+
+        // Substring fractions, max 6 digits for usec
+        for (unsigned int i = 0; i < 6; i++) {
+            if ((*(tokens + i) >= 48) && (*(tokens + i) <= 57)) {
+                fractions[i] = *(tokens + i);
+            } else {
+                break;
+            }
+        }
+
+        // convert fractions to uint
+        status = sscanf(fractions, "%d", &((*t).fraction));
+
+        if (strlen(fractions) == 3) {
+            (*t).fraction = (*t).fraction * 1000; // convert msec to usec
+        } else if (strlen(fractions) == 6) {
+            // all fine, already in usec
+        } else {
+            goto cleanup; // Invalid fractions must be msec or usec
+        }
+
+        // validate
+        if (status != 1) goto cleanup;
+        if ((*t).fraction < 0 || (*t).fraction > 999999) goto cleanup;
+
+        tokens += strlen(fractions);
+
+        // no timezone provided
+        if (strlen(tokens) == 0) {
+            (*t).offset = 0;
+            (*t).ok = 1;
+            goto cleanup;
+        }
+    }
+
+    // parse timezone
+    if ((*tokens == 'Z') || (*tokens == 'z')) {
+        (*t).offset = 0;
+        (*t).ok = 1;
+        goto cleanup;
+    } else if ((*tokens == '+') || (*tokens == '-')) {
+        unsigned int tz_hour, tz_minute;
+
+        status = sscanf(tokens + 1, "%02d:%02d", &tz_hour, &tz_minute);
+
+        // validate
+        if (status != 2) goto cleanup;
+        if ((tz_hour < 0) || (tz_hour > 23)) goto cleanup;
+        if ((tz_minute < 0) || (tz_minute > 59)) goto cleanup;
+
+        // final offset
+        int tz_offset = (tz_hour * HOUR_IN_MINS) + tz_minute;
+
+        // make final offset negative
+        if (*tokens == '-') {
+            tz_offset = tz_offset * -1;
+        }
+
+        (*t).offset = tz_offset;
+        (*t).ok = 1;
+    }
+
+cleanup:
+    free(token_ptr);
+    tokens = NULL;
+    token_ptr = NULL;
+    return;
+}
+
+/*
+ * Parse a RFC3339 date-time
+ * date-time = full-date "T" full-time
+ * Ex. 2007-08-31T16:47:31+00:00 or 2007-12-24T18:21:00.123Z
+ *
+ * Using " " instead of "T" is NOT supported.
+ */
+static void _parse_date_time(char *datetime_string, date_time_struct *dt) {
+    _parse_date(datetime_string, &((*dt).date));
+    if ((*dt).date.ok == 0)
+        return;
+
+    _parse_time(datetime_string, &((*dt).time));
+    if ((*dt).time.ok == 0)
+        return;
+
+    (*dt).ok = 1;
+}
+
+/*
+ * Convert positive and negative timestamp double to date_time_struct
+ * based on gmtime
+ */
+static void _timestamp_to_date_time(double timestamp, date_time_struct *now,
+                                    int offset) {
+    timestamp += (offset * MINUTE_IN_SECS);
+
+    time_t t = (time_t)timestamp;
+    double fraction = (double)((timestamp - (int)timestamp) * 1000000);
+    int usec = fraction >= 0.0 ?\
+        (int)floor(fraction + 0.5) : (int)ceil(fraction - 0.5);
+
+    if (usec < 0) {
+        t -= 1;
+        usec += 1000000;
+    }
+
+    if (usec == 1000000) {
+        t += 1;
+        usec = 0;
+    }
+
+    struct tm *ts = NULL;
+    ts = gmtime(&t);
+
+    (*now).date.year = (*ts).tm_year + 1900;
+    (*now).date.month = (*ts).tm_mon + 1;
+    (*now).date.day = (*ts).tm_mday;
+    (*now).date.wday = (*ts).tm_wday + 1;
+    (*now).date.ok = 1;
+
+    (*now).time.hour = (*ts).tm_hour;
+    (*now).time.minute = (*ts).tm_min;
+    (*now).time.second = (*ts).tm_sec;
+    (*now).time.fraction = (int)usec; // sec fractions in microseconds
+    (*now).time.offset = offset;
+    (*now).time.ok = 1;
+
+    (*now).ok = 1;
+}
+
+/*
+ * Convert positive and negative timestamp double to date_time_struct
+ * based on localtime
+ */
+static void _local_timestamp_to_date_time(double timestamp, date_time_struct *now) {
+    time_t t = (time_t)timestamp;
+    double fraction = (double)((timestamp - (int)timestamp) * 1000000);
+    int usec = fraction >= 0.0 ?\
+        (int)floor(fraction + 0.5) : (int)ceil(fraction - 0.5);
+
+    if (usec < 0) {
+        t -= 1;
+        usec += 1000000;
+    }
+
+    if (usec == 1000000) {
+        t += 1;
+        usec = 0;
+    }
+
+    struct tm *ts = NULL;
+    ts = localtime(&t);
+
... 1149 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-udatetime.git



More information about the Python-modules-commits mailing list