././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1660291772.3493607 udatetime-0.0.17/0000775000175000017500000000000000000000000012617 5ustar00simonsimon././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1660288372.0 udatetime-0.0.17/MANIFEST.in0000664000175000017500000000003600000000000014354 0ustar00simonsimoninclude *.txt *.cfg *.md *.py ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1660291772.3493607 udatetime-0.0.17/PKG-INFO0000664000175000017500000000205100000000000013712 0ustar00simonsimonMetadata-Version: 1.1 Name: udatetime Version: 0.0.17 Summary: Fast RFC3339 compliant date-time library Home-page: https://github.com/freach/udatetime Author: Simon Pirschel Author-email: simon@aboutsimon.com License: Apache 2.0 Description: # udatetime: Fast RFC3339 compliant date-time library Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable 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.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1660288372.0 udatetime-0.0.17/README.md0000664000175000017500000002030200000000000014073 0ustar00simonsimon# 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. The timezones that `udatetime` uses are fixed-offset timezones, meaning that they don't observe daylight savings time (DST), and thus return a fixed offset from UTC all year round. | | 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 from 1DIGIT up to 6DIGIT - 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. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1660288372.0 udatetime-0.0.17/requirements.txt0000664000175000017500000000000000000000000016071 0ustar00simonsimon././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1660291772.3493607 udatetime-0.0.17/scripts/0000775000175000017500000000000000000000000014306 5ustar00simonsimon././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1660288372.0 udatetime-0.0.17/scripts/bench_udatetime.py0000664000175000017500000000617000000000000020004 0ustar00simonsimonfrom __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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1660291772.3493607 udatetime-0.0.17/setup.cfg0000664000175000017500000000004600000000000014440 0ustar00simonsimon[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1660288372.0 udatetime-0.0.17/setup.py0000664000175000017500000000421300000000000014331 0ustar00simonsimonfrom 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')) ext_modules = [] if __pypy__ is None: ext_modules.append( Extension( 'udatetime.rfc3339', ['./src/rfc3339.c'], libraries=['m'], 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 :: 5 - Production/Stable', '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.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], author='Simon Pirschel', author_email='simon@aboutsimon.com', url='https://github.com/freach/udatetime', packages=find_packages(), include_package_data=True, zip_safe=False, install_requires=requires, ext_modules=ext_modules, scripts=['scripts/bench_udatetime.py'], ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1660291772.3493607 udatetime-0.0.17/src/0000775000175000017500000000000000000000000013406 5ustar00simonsimon././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1660290066.0 udatetime-0.0.17/src/rfc3339.c0000664000175000017500000006511000000000000014651 0ustar00simonsimon#define _GNU_SOURCE 1 #if defined(_PYTHON2) || defined(_PYTHON3) #include #include #include #endif #include #include #include #include #include #ifdef HAVE_FTIME #include #endif #define RFC3339_VERSION "0.0.16" #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) ); free((char*)tokens); // 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[7] = {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) < 6 && strlen(fractions) > 0) { (*t).fraction = (*t).fraction * pow(10, 6 - strlen(fractions)); // 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; tokens++; if (strlen(tokens) == 0) { (*t).ok = 1; } else { (*t).ok = 0; } 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; tokens = tokens + 6; if (strlen(tokens) == 0) { (*t).ok = 1; } else { (*t).ok = 0; } } 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); (*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 = 0; (*now).time.ok = 1; (*now).ok = 1; } /* * Create date-time with current values (time now) with given timezone offset * offset = UTC offset in minutes */ #define _now(now, offset) _timestamp_to_date_time(_gettime(), now, offset) /* * Create date-time with current values in UTC */ static void _utcnow(date_time_struct *now) { _now(now, 0); } /* * Create date-time with current values in systems local timezone */ static void _localnow(date_time_struct *now) { _now(now, _get_local_utc_offset()); } /* * Create RFC3339 date-time string */ static void _format_date_time(date_time_struct *dt, char* datetime_string) { int offset = (*dt).time.offset; char sign = '+'; if (offset < 0) { offset = offset * -1; sign = '-'; } sprintf( datetime_string, "%04d-%02d-%02dT%02d:%02d:%02d.%06d%c%02d:%02d", (*dt).date.year, (*dt).date.month, (*dt).date.day, (*dt).time.hour, (*dt).time.minute, (*dt).time.second, (*dt).time.fraction, sign, offset / HOUR_IN_MINS, offset % HOUR_IN_MINS ); } /* * ***======================= C API =======================*** */ typedef struct { double (*get_time)(void); void (*parse_date)(char*, date_struct*); void (*parse_time)(char*, time_struct*); void (*parse_date_time)(char*, date_time_struct*); void (*timestamp_to_date_time)(double, date_time_struct*, int); void (*format_date_time)(date_time_struct*, char*); void (*utcnow)(date_time_struct*); void (*localnow)(date_time_struct*); int (*get_local_utc_offset)(void); } RFC3999_CAPI; extern RFC3999_CAPI CAPI = { _gettime, _parse_date, _parse_time, _parse_date_time, _timestamp_to_date_time, _format_date_time, _utcnow, _localnow, _get_local_utc_offset }; /* * ***======================= CPython Section =======================*** */ #if defined(_PYTHON2) || defined(_PYTHON3) /* * class FixedOffset(tzinfo): */ typedef struct { PyObject_HEAD int offset; } FixedOffset; /* * def __init__(self, offset): * self.offset = offset */ static int FixedOffset_init(FixedOffset *self, PyObject *args, PyObject *kwargs) { int offset; if (!PyArg_ParseTuple(args, "i", &offset)) return -1; self->offset = offset; return 0; } /* * def utcoffset(self, dt): * return timedelta(seconds=self.offset * 60) */ static PyObject *FixedOffset_utcoffset(FixedOffset *self, PyObject *args) { return PyDelta_FromDSU(0, self->offset * 60, 0); } /* * def dst(self, dt): * return timedelta(0) */ static PyObject *FixedOffset_dst(FixedOffset *self, PyObject *args) { return PyDelta_FromDSU(0, 0, 0); } /* * def tzname(self, dt): * sign = '+' * if self.offset < 0: * sign = '-' * return "%s%d:%d" % (sign, self.offset / 60, self.offset % 60) */ static PyObject *FixedOffset_tzname(FixedOffset *self, PyObject *args) { char tzname[7] = {0}; char sign = '+'; int offset = self->offset; if (offset < 0) { sign = '-'; offset *= -1; } sprintf( tzname, "%c%02d:%02d", sign, offset / HOUR_IN_MINS, offset % HOUR_IN_MINS ); #ifdef _PYTHON3 return PyUnicode_FromString(tzname); #else return PyString_FromString(tzname); #endif } /* * def __repr__(self): * return self.tzname() */ static PyObject *FixedOffset_repr(FixedOffset *self) { return FixedOffset_tzname(self, NULL); } /* * Class member / class attributes */ static PyMemberDef FixedOffset_members[] = { {"offset", T_INT, offsetof(FixedOffset, offset), 0, "UTC offset"}, {NULL} }; /* * Class methods */ static PyMethodDef FixedOffset_methods[] = { {"utcoffset", (PyCFunction)FixedOffset_utcoffset, METH_VARARGS, ""}, {"dst", (PyCFunction)FixedOffset_dst, METH_VARARGS, ""}, {"tzname", (PyCFunction)FixedOffset_tzname, METH_VARARGS, ""}, {NULL} }; #ifdef _PYTHON3 static PyTypeObject FixedOffset_type = { PyVarObject_HEAD_INIT(NULL, 0) "rfc3339.FixedOffset_type", /* tp_name */ sizeof(FixedOffset), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ (reprfunc)FixedOffset_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ (reprfunc)FixedOffset_repr, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */ "TZInfo with fixed offset", /* tp_doc */ }; #else static PyTypeObject FixedOffset_type = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "rfc3339.FixedOffset_type", /*tp_name*/ sizeof(FixedOffset), /*tp_basicsize*/ 0, /*tp_itemsize*/ 0, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ (reprfunc)FixedOffset_repr,/*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ (reprfunc)FixedOffset_repr,/*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ "TZInfo with fixed offset",/* tp_doc */ }; #endif /* * Instantiate new FixedOffset_type object * Skip overhead of calling PyObject_New and PyObject_Init. * Directly allocate object. */ static PyObject *new_fixed_offset_ex(int offset, PyTypeObject *type) { FixedOffset *self = (FixedOffset *) (type->tp_alloc(type, 0)); if (self == NULL) { return NULL; } self->offset = offset; return (PyObject *) self; } #define new_fixed_offset(offset) new_fixed_offset_ex(offset, &FixedOffset_type) static PyObject *dtstruct_to_datetime_obj(date_time_struct *dt) { if ((*dt).ok == 1) { PyObject *offset = new_fixed_offset((*dt).time.offset); PyObject *new_datetime = PyDateTimeAPI->DateTime_FromDateAndTime( (*dt).date.year, (*dt).date.month, (*dt).date.day, (*dt).time.hour, (*dt).time.minute, (*dt).time.second, (*dt).time.fraction, offset, PyDateTimeAPI->DateTimeType ); Py_DECREF(offset); if (PyErr_Occurred()) return NULL; return new_datetime; } Py_INCREF(Py_None); return Py_None; } static void check_timestamp_platform_support(double timestamp) { double diff = timestamp - (double)((time_t)timestamp); if (diff <= -1.0 || diff >= 1.0) { PyErr_SetString( PyExc_ValueError, "timestamp out of range for platform time_t" ); } } static void check_date_time_struct(date_time_struct *dt) { if ((*dt).ok != 1) { if ((*dt).date.ok != 1) { PyErr_SetString( PyExc_ValueError, "Invalid RFC3339 date-time string. Date invalid." ); } else if ((*dt).time.ok != 1) { PyErr_SetString( PyExc_ValueError, "Invalid RFC3339 date-time string. Time invalid." ); } else { PyErr_SetString(PyExc_ValueError, "Not supposed to happen!"); } } } static PyObject *utcnow(PyObject *self) { date_time_struct dt = {{0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, 0}; _utcnow(&dt); return dtstruct_to_datetime_obj(&dt); } static PyObject *localnow(PyObject *self) { date_time_struct dt = {{0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, 0}; _localnow(&dt); return dtstruct_to_datetime_obj(&dt); } static PyObject *from_rfc3339_string(PyObject *self, PyObject *args) { char *rfc3339_string; if (!PyArg_ParseTuple(args, "s", &rfc3339_string)) return NULL; date_time_struct dt = {{0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, 0}; _parse_date_time(rfc3339_string, &dt); check_date_time_struct(&dt); if(PyErr_Occurred()) return NULL; return dtstruct_to_datetime_obj(&dt); } static PyObject *to_rfc3339_string(PyObject *self, PyObject *args) { PyObject *obj = NULL; if (!PyArg_ParseTuple(args, "O", &obj)) return NULL; if (!PyDateTime_Check(obj)) { PyErr_SetString(PyExc_ValueError, "Expected a datetime object."); return NULL; } PyDateTime_DateTime *datetime_obj = (PyDateTime_DateTime *)obj; int offset = 0; // TODO: Support all tzinfo subclasses by calling utcoffset() if (datetime_obj->hastzinfo) { if (Py_TYPE(datetime_obj->tzinfo) == &FixedOffset_type) { FixedOffset *tzinfo = (FixedOffset *)datetime_obj->tzinfo; offset = tzinfo->offset; } else { PyErr_SetString(PyExc_ValueError, "Only TZFixedOffset supported."); return NULL; } } date_time_struct dt = { { (datetime_obj->data[0] << 8) | datetime_obj->data[1], datetime_obj->data[2], datetime_obj->data[3], 0, // wday, not needed 1 }, { datetime_obj->data[4], datetime_obj->data[5], datetime_obj->data[6], ( (datetime_obj->data[7] << 16) |\ (datetime_obj->data[8] << 8) |\ datetime_obj->data[9] ), offset, 1 }, 1 }; char datetime_string[33] = {0}; _format_date_time(&dt, datetime_string); #ifdef _PYTHON3 return PyUnicode_FromString(datetime_string); #else return PyString_FromString(datetime_string); #endif } static PyObject *from_timestamp(PyObject *self, PyObject *args, PyObject *kw) { double timestamp; PyObject *tz = Py_None; static char *keywords[] = {"timestamp", "tz", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kw, "d|O", keywords, ×tamp, &tz)) return NULL; check_timestamp_platform_support(timestamp); if(PyErr_Occurred()) return NULL; date_time_struct dt = {{0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, 0}; // TODO: Support all tzinfo subclasses by calling utcoffset() if (tz && tz != Py_None) { if (Py_TYPE(tz) != &FixedOffset_type) { PyErr_Format(PyExc_TypeError, "tz must be of type TZFixedOffset."); return NULL; } else { // Call gmtime based timestamp to datetime convertsion, offset provided _timestamp_to_date_time( timestamp, &dt, ((FixedOffset *)tz)->offset ); } } else { // Call localtime based timestamp to datetime convertsion, no offset // provided, account for daylight saving _local_timestamp_to_date_time(timestamp, &dt); } check_date_time_struct(&dt); if(PyErr_Occurred()) return NULL; return dtstruct_to_datetime_obj(&dt); } static PyObject *from_utctimestamp(PyObject *self, PyObject *args) { double timestamp; if (!PyArg_ParseTuple(args, "d", ×tamp)) return NULL; check_timestamp_platform_support(timestamp); if(PyErr_Occurred()) return NULL; date_time_struct dt = {{0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, 0}; _timestamp_to_date_time(timestamp, &dt, 0); check_date_time_struct(&dt); if(PyErr_Occurred()) return NULL; return dtstruct_to_datetime_obj(&dt); } static PyObject *utcnow_to_string(PyObject *self) { date_time_struct dt = {{0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, 0}; _utcnow(&dt); char datetime_string[33] = {0}; _format_date_time(&dt, datetime_string); #ifdef _PYTHON3 return PyUnicode_FromString(datetime_string); #else return PyString_FromString(datetime_string); #endif } static PyObject *localnow_to_string(PyObject *self) { date_time_struct dt = {{0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, 0}; _localnow(&dt); char datetime_string[33] = {0}; _format_date_time(&dt, datetime_string); #ifdef _PYTHON3 return PyUnicode_FromString(datetime_string); #else return PyString_FromString(datetime_string); #endif } // static PyObject *bench_c(PyObject *self) { // return Py_None; // } static PyMethodDef rfc3339_methods[] = { { "utcnow", (PyCFunction) utcnow, METH_NOARGS, PyDoc_STR("datetime aware object in UTC with current date and time.") }, { "now", (PyCFunction) localnow, METH_NOARGS, PyDoc_STR( "datetime aware object in local timezone with current date and time." ) }, { "from_timestamp", (PyCFunction) from_timestamp, METH_VARARGS | METH_KEYWORDS, PyDoc_STR("timestamp[, tz] -> tz's local time from POSIX timestamp.") }, { "from_utctimestamp", (PyCFunction)from_utctimestamp, METH_VARARGS, PyDoc_STR( "timestamp -> UTC datetime from a POSIX timestamp (like time.time())." ) }, { "from_rfc3339_string", (PyCFunction) from_rfc3339_string, METH_VARARGS, PyDoc_STR("Parse RFC3339 compliant date-time string.") }, { "to_rfc3339_string", (PyCFunction) to_rfc3339_string, METH_VARARGS, PyDoc_STR("Serialize datetime to RFC3339 compliant date-time string.") }, { "utcnow_to_string", (PyCFunction) utcnow_to_string, METH_NOARGS, PyDoc_STR("Current UTC date and time RFC3339 compliant date-time string.") }, { "now_to_string", (PyCFunction) localnow_to_string, METH_NOARGS, PyDoc_STR("Local date and time RFC3339 compliant date-time string.") }, {NULL} }; #ifdef _PYTHON3 static struct PyModuleDef Python3_module = { PyModuleDef_HEAD_INIT, "udatetime.rfc3339", NULL, -1, rfc3339_methods, NULL, NULL, NULL, NULL, }; #endif PyMODINIT_FUNC #ifdef _PYTHON3 PyInit_rfc3339(void) #else initrfc3339(void) #endif { _get_local_utc_offset(); // call once to set local_utc_offset PyObject *m; PyObject *version_string; PyDateTime_IMPORT; #ifdef _PYTHON3 m = PyModule_Create(&Python3_module); #else m = Py_InitModule("udatetime.rfc3339", rfc3339_methods); #endif if (m == NULL) #ifdef _PYTHON3 return NULL; #else return; #endif #ifdef _PYTHON3 version_string = PyUnicode_FromString(RFC3339_VERSION); #else version_string = PyString_FromString(RFC3339_VERSION); #endif PyModule_AddObject(m, "__version__", version_string); FixedOffset_type.tp_new = PyType_GenericNew; FixedOffset_type.tp_base = PyDateTimeAPI->TZInfoType; FixedOffset_type.tp_methods = FixedOffset_methods; FixedOffset_type.tp_members = FixedOffset_members; FixedOffset_type.tp_init = (initproc)FixedOffset_init; if (PyType_Ready(&FixedOffset_type) < 0) #ifdef _PYTHON3 return NULL; #else return; #endif Py_INCREF(&FixedOffset_type); PyModule_AddObject(m, "TZFixedOffset", (PyObject *)&FixedOffset_type); #ifdef _PYTHON3 return m; #endif } #endif ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1660291772.3493607 udatetime-0.0.17/test/0000775000175000017500000000000000000000000013576 5ustar00simonsimon././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1660290121.0 udatetime-0.0.17/test/test_udatetime.py0000664000175000017500000002113200000000000017167 0ustar00simonsimonimport unittest from datetime import datetime, timedelta, tzinfo import udatetime NO_DST = timedelta(0) class Test(unittest.TestCase): def test_utcnow(self): dt_now = datetime.utcnow() now = udatetime.utcnow() self.assertIsInstance(now, datetime) self.assertEqual(now.year, dt_now.year) self.assertEqual(now.month, dt_now.month) self.assertEqual(now.day, dt_now.day) self.assertEqual(now.hour, dt_now.hour) self.assertEqual(now.minute, dt_now.minute) self.assertEqual(now.second, dt_now.second) # self.assertEqual(now.microsecond, dt_now.microsecond) self.assertEqual(now.utcoffset(), timedelta(0)) self.assertEqual(now.dst(), NO_DST) def test_now(self): dt_now = datetime.now() now = udatetime.now() self.assertIsInstance(now, datetime) self.assertEqual(now.year, dt_now.year) self.assertEqual(now.month, dt_now.month) self.assertEqual(now.day, dt_now.day) self.assertEqual(now.hour, dt_now.hour) self.assertEqual(now.minute, dt_now.minute) self.assertEqual(now.second, dt_now.second) # self.assertEqual(now.microsecond, dt_now.microsecond) def test_from_and_to_string(self): rfc3339 = '2016-07-15T12:33:20.123000+01:30' dt = udatetime.from_string(rfc3339) self.assertIsInstance(dt, datetime) self.assertEqual(dt.year, 2016) self.assertEqual(dt.month, 7) self.assertEqual(dt.day, 15) self.assertEqual(dt.hour, 12) self.assertEqual(dt.minute, 33) self.assertEqual(dt.second, 20) self.assertEqual(dt.microsecond, 123000) self.assertEqual(dt.utcoffset(), timedelta(hours=1, minutes=30)) self.assertEqual(dt.dst(), NO_DST) self.assertEqual(udatetime.to_string(dt), rfc3339) rfc3339 = '2016-07-18T12:58:26.485897-02:00' dt = udatetime.from_string(rfc3339) self.assertEqual(udatetime.to_string(dt), rfc3339) def test_fromtimestamp(self): DAY = 86400 HOUR = 3600 TZ_CEST = udatetime.TZFixedOffset(60 * 2) for t in range(0, DAY - (2 * HOUR), HOUR): dt = datetime.fromtimestamp(t) udt = udatetime.fromtimestamp(t) self.assertIsInstance(udt, datetime) self.assertEqual(udt.year, dt.year) self.assertEqual(udt.month, dt.month) self.assertEqual(udt.day, dt.day) self.assertEqual(udt.hour, dt.hour) self.assertEqual(udt.minute, dt.minute) self.assertEqual(udt.second, dt.second) self.assertEqual(udt.microsecond, dt.microsecond) self.assertEqual(udt.utcoffset(), timedelta(0)) self.assertEqual(udt.dst(), NO_DST) for t in range(0, DAY, HOUR): dt = datetime.fromtimestamp(t, TZ_CEST) udt = udatetime.fromtimestamp(t, TZ_CEST) self.assertIsInstance(udt, datetime) self.assertEqual(udt.year, dt.year) self.assertEqual(udt.month, dt.month) self.assertEqual(udt.day, dt.day) self.assertEqual(udt.hour, dt.hour) self.assertEqual(udt.minute, dt.minute) self.assertEqual(udt.second, dt.second) self.assertEqual(udt.microsecond, dt.microsecond) self.assertEqual(udt.utcoffset(), timedelta(hours=2)) self.assertEqual(udt.dst(), NO_DST) for t in range(0, DAY * -1, HOUR * -1): dt = datetime.fromtimestamp(t, TZ_CEST) udt = udatetime.fromtimestamp(t, TZ_CEST) self.assertIsInstance(udt, datetime) self.assertEqual(udt.year, dt.year) self.assertEqual(udt.month, dt.month) self.assertEqual(udt.day, dt.day) self.assertEqual(udt.hour, dt.hour) self.assertEqual(udt.minute, dt.minute) self.assertEqual(udt.second, dt.second) self.assertEqual(udt.microsecond, dt.microsecond) self.assertEqual(udt.utcoffset(), timedelta(hours=2)) self.assertEqual(udt.dst(), NO_DST) def test_utcfromtimestamp(self): DAY = 86400 HOUR = 3600 for t in range(0, DAY, HOUR): dt = datetime.utcfromtimestamp(t) udt = udatetime.utcfromtimestamp(t) self.assertIsInstance(udt, datetime) self.assertEqual(udt.year, dt.year) self.assertEqual(udt.month, dt.month) self.assertEqual(udt.day, dt.day) self.assertEqual(udt.hour, dt.hour) self.assertEqual(udt.minute, dt.minute) self.assertEqual(udt.second, dt.second) self.assertEqual(udt.microsecond, dt.microsecond) self.assertEqual(udt.utcoffset(), timedelta(0)) self.assertEqual(udt.dst(), NO_DST) for t in range(0, DAY * -1, HOUR * -1): dt = datetime.utcfromtimestamp(t) udt = udatetime.utcfromtimestamp(t) self.assertIsInstance(udt, datetime) self.assertEqual(udt.year, dt.year) self.assertEqual(udt.month, dt.month) self.assertEqual(udt.day, dt.day) self.assertEqual(udt.hour, dt.hour) self.assertEqual(udt.minute, dt.minute) self.assertEqual(udt.second, dt.second) self.assertEqual(udt.microsecond, dt.microsecond) self.assertEqual(udt.utcoffset(), timedelta(0)) self.assertEqual(udt.dst(), NO_DST) def test_broken_from_string(self): invalid = [ '2016-07-15 12:33:20.123000+01:30', '2016-13-15T12:33:20.123000+01:30', '20161315T12:33:20.123000+01:30', 'Hello World', '2016-07-15 12:33:20.123000+01:302016-07-15 12:33:20.123000+01:30', '2016-07-15T12:33:20.1Z0', '2016-07-15T12:33:20.1 +01:30f', ] for r in invalid: with self.assertRaises(ValueError): udatetime.from_string(r) def test_ok_from_string(self): rfc3339s = [ '2016-07-15 T 12:33:20.123000 +01:30', '2016-07-15 T 12:33:20.123000 +01:30', '2016-07-15T12:33:20.123 +01:30', '2016-07-15T12:33:20 +01:30', '2016-07-15T12:33:20 Z', '2016-07-15T12:33:20', '2016-07-15t12:33:20', '2016-07-15T12:33:20.1 +01:30', ] for r in rfc3339s: self.assertIsInstance( udatetime.from_string(r), datetime ) def test_tzone(self): rfc3339 = '2016-07-15T12:33:20.123000+01:30' dt = udatetime.from_string(rfc3339) offset = dt.tzinfo.utcoffset() dst = dt.tzinfo.dst() self.assertIsInstance(offset, timedelta) self.assertEqual(offset.total_seconds() / 60, 90) self.assertEqual(dst, NO_DST) rfc3339 = '2016-07-15T12:33:20.123000Z' dt = udatetime.from_string(rfc3339) offset = dt.tzinfo.utcoffset() dst = dt.tzinfo.dst() self.assertIsInstance(offset, timedelta) self.assertEqual(offset.total_seconds(), 0) self.assertEqual(dst, NO_DST) rfc3339 = '2016-07-15T12:33:20.123000-02:00' dt = udatetime.from_string(rfc3339) offset = dt.tzinfo.utcoffset() dst = dt.tzinfo.dst() self.assertIsInstance(offset, timedelta) self.assertEqual(offset.total_seconds() / 60, -120) self.assertEqual(dst, NO_DST) def test_precision(self): t = 1469897308.549871 dt = datetime.fromtimestamp(t) udt = udatetime.fromtimestamp(t) self.assertEqual(udt.microsecond, dt.microsecond) def test_raise_on_not_TZFixedOffset(self): class TZInvalid(tzinfo): def utcoffset(self, dt=None): return timedelta(seconds=0) def dst(self, dt=None): return timedelta(seconds=0) dt = datetime.now(TZInvalid()) with self.assertRaises(ValueError): udatetime.to_string(dt) def test_variable_fraction(self): rfc3339 = '2016-07-15T12:33:20.1' d1 = udatetime.from_string(rfc3339 + ('0' * 5) + 'Z') for x in range(0, 6): d2 = udatetime.from_string(rfc3339 + ('0' * x) + 'Z') self.assertEqual(d1, d2) self.assertEqual( udatetime.from_string('2016-07-15T12:33:20.123Z'), udatetime.from_string('2016-07-15T12:33:20.123000Z'), ) self.assertEqual( udatetime.from_string('2016-07-15T12:33:20.0Z'), udatetime.from_string('2016-07-15T12:33:20Z'), ) if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1660291772.3493607 udatetime-0.0.17/udatetime/0000775000175000017500000000000000000000000014600 5ustar00simonsimon././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1660288372.0 udatetime-0.0.17/udatetime/__init__.py0000664000175000017500000000157500000000000016721 0ustar00simonsimon# -*- coding: utf-8 -*- try: import __pypy__ except ImportError: __pypy__ = None if __pypy__: from udatetime._pure import ( utcnow, now, from_rfc3339_string as from_string, to_rfc3339_string as to_string, utcnow_to_string, now_to_string, from_timestamp as fromtimestamp, from_utctimestamp as utcfromtimestamp, TZFixedOffset ) else: from udatetime.rfc3339 import ( utcnow, now, from_rfc3339_string as from_string, to_rfc3339_string as to_string, utcnow_to_string, now_to_string, from_timestamp as fromtimestamp, from_utctimestamp as utcfromtimestamp, TZFixedOffset ) __all__ = [ 'utcnow', 'now', 'from_string', 'to_string', 'utcnow_to_string', 'now_to_string', 'fromtimestamp', 'utcfromtimestamp', 'TZFixedOffset' ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1660288372.0 udatetime-0.0.17/udatetime/_pure.py0000664000175000017500000001402200000000000016263 0ustar00simonsimonfrom datetime import tzinfo, timedelta, datetime as dt_datetime from time import time, gmtime from math import floor, ceil DATE_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%f' class TZFixedOffset(tzinfo): def __init__(self, offset): self.offset = offset def utcoffset(self, dt=None): return timedelta(seconds=self.offset * 60) def dst(self, dt=None): return timedelta(0) def tzname(self, dt=None): sign = '+' if self.offset < 0: sign = '-' return "%s%d:%d" % (sign, self.offset / 60, self.offset % 60) def __repr__(self): return self.tzname() def _timestamp_to_date_time(timestamp, tzinfo): t_full = timestamp + (tzinfo.offset * 60) timestamp = int(floor(t_full)) frac = (t_full - timestamp) * 1e6 us = int(floor(frac + 0.5) if frac >= 0.0 else ceil(frac - 0.5)) if us == 1e6: timestamp += 1 us = 0 y, m, d, hh, mm, ss, weekday, jday, dst = gmtime(timestamp) ss = min(ss, 59) # if sec > 59, set 59 (platform leap support) return dt_datetime(y, m, d, hh, mm, ss, us, tzinfo) def _format_date_time(date_time): tm = date_time.timetuple() offset = 0 sign = '+' if date_time.tzinfo is not None: if date_time.tzinfo.__class__ is not TZFixedOffset: # TODO: Support all tzinfo subclasses by calling utcoffset() raise ValueError('Only TZFixedOffset supported.') offset = date_time.tzinfo.offset if offset < 0: offset = offset * -1 sign = '-' return '%04d-%02d-%02dT%02d:%02d:%02d.%06d%c%02d:%02d' % ( tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, date_time.microsecond, sign, offset / 60, offset % 60 ) def _get_local_utc_offset(): ts = time() return ( dt_datetime.fromtimestamp(ts) - dt_datetime.utcfromtimestamp(ts) ).total_seconds() / 60 local_utc_offset = _get_local_utc_offset() local_timezone = TZFixedOffset(local_utc_offset) utc_timezone = TZFixedOffset(0) def utcnow(): '''datetime aware object in UTC with current date and time.''' return _timestamp_to_date_time(time(), utc_timezone) def now(): '''datetime aware object in local timezone with current date and time.''' return _timestamp_to_date_time(time(), local_timezone) def from_rfc3339_string(rfc3339_string): '''Parse RFC3339 compliant date-time string.''' rfc3339_string = rfc3339_string.replace(' ', '').lower() if 't' not in rfc3339_string: raise ValueError( 'Invalid RFC3339 string. Missing \'T\' date/time separator.' ) (date, _, _time) = rfc3339_string.partition('t') if not date or not _time: raise ValueError('Invalid RFC3339 string.') try: (year, month, day) = date.split('-') year = int(year) month = int(month) day = int(day) except ValueError: raise ValueError('Invalid RFC3339 string. Invalid date.') try: (hour, minute, second) = _time[:8].split(':') hour = int(hour) minute = int(minute) second = int(second) except ValueError: raise ValueError('Invalid RFC3339 string. Invalid time.') usec = 0 offset = None if len(_time) > 8: if _time[8] == '.': usec_buf = '' for c in _time[9:]: if c in '0123456789': usec_buf += c else: break if len(usec_buf) > 6: raise ValueError('Invalid RFC3339 string. Invalid fractions.') usec = int(usec_buf) if len(usec_buf) > 0 and len(usec_buf) < 6: # ugly as shit, but good damn multiplication precision makes # it a mess usec = usec * int('1' + '0' * (6 - len(usec_buf))) _time = _time[9 + len(usec_buf):] elif _time[8] == 'z': offset = 0 if len(_time[9:]): raise ValueError( 'Invalid RFC3339 string. Remaining data after time zone.' ) else: _time = _time[8:] else: offset = 0 if offset is None and (len(_time) == 0 or _time[0] == 'z'): offset = 0 if len(_time[1:]): raise ValueError( 'Invalid RFC3339 string. Remaining data after time zone.' ) elif offset is None: if _time[0] not in '+-': raise ValueError('Invalid RFC3339 string. Expected timezone.') negative = True if _time[0] == '-' else False try: (off_hour, off_minute) = _time[1:].split(':') off_hour = int(off_hour) off_minute = int(off_minute) except ValueError: raise ValueError('Invalid RFC3339 string. Invalid timezone.') offset = (off_hour * 60) + off_minute if negative: offset = offset * -1 return dt_datetime( year, month, day, hour, minute, second, usec, TZFixedOffset(offset) ) def to_rfc3339_string(date_time): '''Serialize date_time to RFC3339 compliant date-time string.''' if date_time and date_time.__class__ is not dt_datetime: raise ValueError("Expected a datetime object.") return _format_date_time(date_time) def from_timestamp(timestamp, tz=None): '''timestamp[, tz] -> tz's local time from POSIX timestamp.''' if tz is None: tz = local_timezone elif tz.__class__ is not TZFixedOffset: # TODO: Support all tzinfo subclasses by calling utcoffset() raise ValueError('Only TZFixedOffset supported.') return _timestamp_to_date_time(timestamp, tz) def from_utctimestamp(timestamp): '''timestamp -> UTC datetime from a POSIX timestamp (like time.time()).''' return _timestamp_to_date_time(timestamp, utc_timezone) def utcnow_to_string(): '''Current UTC date and time RFC3339 compliant date-time string.''' return _format_date_time(utcnow()) def now_to_string(): '''Local date and time RFC3339 compliant date-time string.''' return _format_date_time(now()) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1660291772.3493607 udatetime-0.0.17/udatetime.egg-info/0000775000175000017500000000000000000000000016272 5ustar00simonsimon././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1660291771.0 udatetime-0.0.17/udatetime.egg-info/PKG-INFO0000664000175000017500000000205100000000000017365 0ustar00simonsimonMetadata-Version: 1.1 Name: udatetime Version: 0.0.17 Summary: Fast RFC3339 compliant date-time library Home-page: https://github.com/freach/udatetime Author: Simon Pirschel Author-email: simon@aboutsimon.com License: Apache 2.0 Description: # udatetime: Fast RFC3339 compliant date-time library Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable 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.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1660291771.0 udatetime-0.0.17/udatetime.egg-info/SOURCES.txt0000664000175000017500000000051200000000000020154 0ustar00simonsimonMANIFEST.in README.md requirements.txt setup.py version.txt ./src/rfc3339.c scripts/bench_udatetime.py test/test_udatetime.py udatetime/__init__.py udatetime/_pure.py udatetime.egg-info/PKG-INFO udatetime.egg-info/SOURCES.txt udatetime.egg-info/dependency_links.txt udatetime.egg-info/not-zip-safe udatetime.egg-info/top_level.txt././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1660291771.0 udatetime-0.0.17/udatetime.egg-info/dependency_links.txt0000664000175000017500000000000100000000000022340 0ustar00simonsimon ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1660291771.0 udatetime-0.0.17/udatetime.egg-info/not-zip-safe0000664000175000017500000000000100000000000020520 0ustar00simonsimon ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1660291771.0 udatetime-0.0.17/udatetime.egg-info/top_level.txt0000664000175000017500000000001200000000000021015 0ustar00simonsimonudatetime ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1660288372.0 udatetime-0.0.17/version.txt0000664000175000017500000000000700000000000015042 0ustar00simonsimon0.0.17