kopia lustrzana https://github.com/ukhas/habitat-cpp-connector
Add RFC3339
rodzic
cce6d3f2fa
commit
6ed70acd99
|
@ -1,4 +1 @@
|
|||
cpp_connector
|
||||
cpp_connector_threaded
|
||||
extractor
|
||||
*.o
|
||||
|
|
19
Makefile
19
Makefile
|
@ -6,15 +6,19 @@ jsoncpp_cflags := $(shell pkg-config --cflags jsoncpp)
|
|||
jsoncpp_libs := $(shell pkg-config --libs jsoncpp)
|
||||
|
||||
CFLAGS = -pthread -O2 -Wall -Werror -pedantic -Wno-long-long \
|
||||
-Wno-variadic-macros -Isrc $(jsoncpp_cflags)
|
||||
-Wno-variadic-macros -Isrc $(jsoncpp_cflags)
|
||||
upl_libs = -pthread $(jsoncpp_libs) -lcurl -lssl
|
||||
ext_libs = $(jsoncpp_libs)
|
||||
rfc_libs = $(jsoncpp_libs)
|
||||
|
||||
test_py_files = tests/test_uploader.py tests/test_extractor.py
|
||||
headers = src/CouchDB.h src/EZ.h src/Uploader.h src/UploaderThread.h \
|
||||
headers = src/CouchDB.h src/EZ.h src/RFC3339.h \
|
||||
src/Uploader.h src/UploaderThread.h \
|
||||
src/Extractor.h src/UKHASExtractor.h \
|
||||
tests/test_extractor_mocks.h
|
||||
upl_cxxfiles = src/CouchDB.cxx src/EZ.cxx src/Uploader.cxx
|
||||
tests/test_extractor_mocks.h
|
||||
rfc_cxxfiles = src/RFC3339.cxx tests/test_rfc3339_main.cxx
|
||||
rfc_binary = tests/rfc3339
|
||||
upl_cxxfiles = src/CouchDB.cxx src/EZ.cxx src/RFC3339.cxx src/Uploader.cxx
|
||||
upl_thr_cflags = -DTHREADED
|
||||
upl_nrm_binary = tests/cpp_connector
|
||||
upl_nrm_objects = tests/test_uploader_main.o
|
||||
|
@ -28,6 +32,7 @@ ext_mock_cflags = -include tests/test_extractor_mocks.h
|
|||
CXXFLAGS = $(CFLAGS)
|
||||
upl_objects = $(patsubst %.cxx,%.o,$(upl_cxxfiles))
|
||||
ext_objects = $(patsubst %.cxx,%.ext_mock.o,$(ext_cxxfiles))
|
||||
rfc_objects = $(patsubst %.cxx,%.o,$(rfc_cxxfiles))
|
||||
|
||||
%.o : %.cxx $(headers)
|
||||
g++ -c $(CXXFLAGS) -o $@ $<
|
||||
|
@ -47,7 +52,11 @@ $(upl_thr_binary) : $(upl_objects) $(upl_thr_objects)
|
|||
$(ext_binary) : $(ext_objects)
|
||||
g++ $(CXXFLAGS) -o $@ $(ext_objects) $(ext_libs)
|
||||
|
||||
test : $(upl_nrm_binary) $(upl_thr_binary) $(ext_binary) $(test_py_files)
|
||||
$(rfc_binary) : $(rfc_objects)
|
||||
g++ $(CXXFLAGS) -o $@ $(rfc_objects) $(rfc_libs)
|
||||
|
||||
test : $(upl_nrm_binary) $(upl_thr_binary) $(ext_binary) $(rfc_binary) \
|
||||
$(test_py_files)
|
||||
nosetests
|
||||
|
||||
clean :
|
||||
|
|
|
@ -0,0 +1,324 @@
|
|||
/* Copyright 2012 (C) Daniel Richman. License: GNU GPL 3; see LICENSE. */
|
||||
|
||||
#include "RFC3339.h"
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <ctime>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
#include <iomanip>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace RFC3339 {
|
||||
|
||||
bool validate_rfc3339(const string &rfc3339)
|
||||
{
|
||||
try
|
||||
{
|
||||
rfc3339_to_timestamp(rfc3339);
|
||||
}
|
||||
catch (InvalidFormat e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Class to be used when extracting from an istream that consumes a single
|
||||
* delimiter character */
|
||||
class Delim
|
||||
{
|
||||
char expect;
|
||||
|
||||
public:
|
||||
Delim(char _e) : expect(_e) {};
|
||||
~Delim() {};
|
||||
void extract(istream &in);
|
||||
};
|
||||
|
||||
/* Delim can't be a reference here because in its intended use it is
|
||||
* constructed on the spot like this ... >> Delim('-') >> ... and therefore
|
||||
* it does not have an address */
|
||||
istream & operator>>(istream &in, Delim delim)
|
||||
{
|
||||
delim.extract(in);
|
||||
return in;
|
||||
}
|
||||
|
||||
void Delim::extract(istream &in)
|
||||
{
|
||||
if (!in.good() || in.get() != expect)
|
||||
in.setstate(ios_base::badbit);
|
||||
}
|
||||
|
||||
/* Extracts an integer of particular length, containing the digits 0-9 only */
|
||||
class StrictInt
|
||||
{
|
||||
size_t length;
|
||||
int ⌖
|
||||
bool check_range;
|
||||
int min, max;
|
||||
|
||||
public:
|
||||
StrictInt(size_t _l, int &_t)
|
||||
: length(_l), target(_t), check_range(false), min(0), max(0) {};
|
||||
StrictInt(size_t _l, int &_t, int _min, int _max)
|
||||
: length(_l), target(_t), check_range(true), min(_min), max(_max) {};
|
||||
~StrictInt() {};
|
||||
void extract(istream &in);
|
||||
};
|
||||
|
||||
istream & operator>>(istream &in, StrictInt tgt)
|
||||
{
|
||||
tgt.extract(in);
|
||||
return in;
|
||||
}
|
||||
|
||||
void StrictInt::extract(istream &in)
|
||||
{
|
||||
if (!in.good())
|
||||
{
|
||||
in.setstate(ios_base::badbit);
|
||||
return;
|
||||
}
|
||||
|
||||
char *temp = new char[length + 1];
|
||||
in.read(temp, length);
|
||||
temp[length] = 0;
|
||||
|
||||
if (in.fail() || strlen(temp) != length)
|
||||
return;
|
||||
|
||||
istringstream temp_ss(temp);
|
||||
temp_ss >> target;
|
||||
|
||||
if (temp_ss.fail() || temp_ss.peek() != EOF)
|
||||
in.setstate(ios_base::badbit);
|
||||
|
||||
if (check_range && (target < min || target > max))
|
||||
in.setstate(ios_base::badbit);
|
||||
}
|
||||
|
||||
static int mdays[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
static int mydays[] = {0, 0, 31, 59, 90, 120, 151, 181, 212,
|
||||
243, 273, 304, 334};
|
||||
|
||||
/* Returns the number of multiples of n in [a,b] */
|
||||
static int multiples_between(int n, int a, int b)
|
||||
{
|
||||
if (a % n != 0)
|
||||
a += n - (a % n);
|
||||
b -= (b % n);
|
||||
return ((b - a) / n) + 1;
|
||||
}
|
||||
|
||||
/* Not provided on all platforms :-(. */
|
||||
static long long int my_timegm(int year, int month, int mday,
|
||||
int hour, int min, int sec)
|
||||
{
|
||||
/* I don't know the best way to get everything promoted to 64bit
|
||||
* integers in the final line, this might do it */
|
||||
long long int epoch_days = 0;
|
||||
bool leap_year = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
|
||||
|
||||
if (year > 1970)
|
||||
{
|
||||
int leap_years = multiples_between(4, 1970, year - 1)
|
||||
- multiples_between(100, 1970, year - 1)
|
||||
+ multiples_between(400, 1970, year - 1);
|
||||
epoch_days = ((year - 1970) * 365) + leap_years;
|
||||
}
|
||||
else if (year < 1970)
|
||||
{
|
||||
int leap_years = multiples_between(4, year, 1970 - 1)
|
||||
- multiples_between(100, year, 1970 - 1)
|
||||
+ multiples_between(400, year, 1970 - 1);
|
||||
epoch_days = -(((1970 - year) * 365) + leap_years);
|
||||
}
|
||||
|
||||
epoch_days += mydays[month];
|
||||
if (month > 2 && leap_year)
|
||||
epoch_days++;
|
||||
epoch_days += mday - 1;
|
||||
|
||||
return (((((epoch_days * 24) + hour) * 60) + min) * 60) + sec;
|
||||
}
|
||||
|
||||
static long long int my_timegm(struct tm tm)
|
||||
{
|
||||
return my_timegm(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec);
|
||||
}
|
||||
|
||||
/* Thin wrappers adding exceptions and range checks */
|
||||
static struct tm my_localtime(long long int timestamp)
|
||||
{
|
||||
time_t timestamp_s = timestamp;
|
||||
if (timestamp_s != timestamp)
|
||||
throw out_of_range("timestamp too large for time_t");
|
||||
|
||||
struct tm tm;
|
||||
if (localtime_r(×tamp_s, &tm) == NULL)
|
||||
throw runtime_error("localtime_r failed");
|
||||
|
||||
return tm;
|
||||
}
|
||||
|
||||
static struct tm my_gmtime(long long int timestamp)
|
||||
{
|
||||
time_t timestamp_s = timestamp;
|
||||
if (timestamp_s != timestamp)
|
||||
throw out_of_range("timestamp too large for time_t");
|
||||
|
||||
struct tm tm;
|
||||
if (gmtime_r(×tamp_s, &tm) == NULL)
|
||||
throw runtime_error("gmtime_r failed");
|
||||
|
||||
return tm;
|
||||
}
|
||||
|
||||
long long int rfc3339_to_timestamp(const string &rfc3339)
|
||||
{
|
||||
istringstream temp(rfc3339);
|
||||
|
||||
int year, month, mday, hour, min, sec;
|
||||
|
||||
temp >> StrictInt(4, year) >> Delim('-')
|
||||
>> StrictInt(2, month, 1, 12) >> Delim('-')
|
||||
>> StrictInt(2, mday, 1, 31) >> Delim('T')
|
||||
>> StrictInt(2, hour, 0, 23) >> Delim(':')
|
||||
>> StrictInt(2, min, 0, 59) >> Delim(':')
|
||||
>> StrictInt(2, sec, 0, 59);
|
||||
|
||||
if (temp.fail() || temp.eof() || temp.tellg() != 19)
|
||||
throw InvalidFormat();
|
||||
|
||||
bool leap_year = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
|
||||
int this_mdays = mdays[month];
|
||||
|
||||
if (leap_year && month == 2)
|
||||
this_mdays++;
|
||||
|
||||
if (mday > this_mdays)
|
||||
throw InvalidFormat();
|
||||
|
||||
if (temp.peek() == '.')
|
||||
{
|
||||
/* discard seconds part */
|
||||
do
|
||||
temp.get();
|
||||
while (temp.good() && temp.peek() >= '0' && temp.peek() <= '9');
|
||||
}
|
||||
|
||||
int offset = 0;
|
||||
int offset_first_char = temp.get();
|
||||
|
||||
if (offset_first_char == 'Z')
|
||||
{
|
||||
/* UTC offset, 0 */
|
||||
}
|
||||
else if (offset_first_char == '+' || offset_first_char == '-')
|
||||
{
|
||||
int offset_hours, offset_minutes;
|
||||
temp >> StrictInt(2, offset_hours, 0, 23) >> Delim(':')
|
||||
>> StrictInt(2, offset_minutes, 0, 59);
|
||||
|
||||
if (temp.fail())
|
||||
throw InvalidFormat();
|
||||
|
||||
offset = offset_hours * 3600 + offset_minutes * 60;
|
||||
|
||||
if (offset_first_char == '-')
|
||||
offset = -offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw InvalidFormat();
|
||||
}
|
||||
|
||||
if (temp.peek() != EOF)
|
||||
throw InvalidFormat();
|
||||
|
||||
return my_timegm(year, month, mday, hour, min, sec) - offset;
|
||||
}
|
||||
|
||||
static string make_datestring_start(int year, int month, int mday,
|
||||
int hour, int min, int sec)
|
||||
{
|
||||
ostringstream temp;
|
||||
temp.fill('0');
|
||||
|
||||
temp << setw(4) << year << '-'
|
||||
<< setw(2) << month << '-'
|
||||
<< setw(2) << mday << 'T'
|
||||
<< setw(2) << hour << ':'
|
||||
<< setw(2) << min << ':'
|
||||
<< setw(2) << sec;
|
||||
|
||||
return temp.str();
|
||||
}
|
||||
|
||||
static string make_datestring_start(struct tm tm)
|
||||
{
|
||||
return make_datestring_start(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec);
|
||||
}
|
||||
|
||||
string timestamp_to_rfc3339_utcoffset(long long int timestamp)
|
||||
{
|
||||
struct tm tm = my_gmtime(timestamp);
|
||||
string ret = make_datestring_start(tm) + "Z";
|
||||
#ifndef NDEBUG
|
||||
if (rfc3339_to_timestamp(ret) != timestamp)
|
||||
throw runtime_error("reparse sanity check failed");
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
string timestamp_to_rfc3339_localoffset(long long int timestamp)
|
||||
{
|
||||
struct tm tm = my_localtime(timestamp);
|
||||
struct tm gm_tm = my_gmtime(timestamp);
|
||||
|
||||
int offset = my_timegm(tm) - my_timegm(gm_tm);
|
||||
|
||||
if (abs(offset) % 60 != 0)
|
||||
throw runtime_error("Your local offset is not a whole minute");
|
||||
|
||||
int offset_minutes = abs(offset) / 60;
|
||||
int offset_hours = offset_minutes / 60;
|
||||
offset_minutes %= 60;
|
||||
|
||||
ostringstream temp;
|
||||
temp << make_datestring_start(tm);
|
||||
temp.fill('0');
|
||||
|
||||
temp << (offset < 0 ? '-' : '+')
|
||||
<< setw(2) << offset_hours << ':'
|
||||
<< setw(2) << offset_minutes;
|
||||
|
||||
string ret = temp.str();
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (rfc3339_to_timestamp(ret) != timestamp)
|
||||
throw runtime_error("reparse sanity check failed");
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
string now_to_rfc3339_utcoffset()
|
||||
{
|
||||
return timestamp_to_rfc3339_utcoffset(time(NULL));
|
||||
}
|
||||
|
||||
string now_to_rfc3339_localoffset()
|
||||
{
|
||||
return timestamp_to_rfc3339_localoffset(time(NULL));
|
||||
}
|
||||
|
||||
} /* namespace RFC3339 */
|
|
@ -0,0 +1,38 @@
|
|||
/* Copyright 2012 (C) Daniel Richman. License: GNU GPL 3; see LICENSE. */
|
||||
|
||||
#ifndef HABITAT_RFC3339_H
|
||||
|
||||
#include <string>
|
||||
#include <ctime>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace RFC3339 {
|
||||
|
||||
class InvalidFormat : public invalid_argument
|
||||
{
|
||||
public:
|
||||
InvalidFormat() : invalid_argument("RFC3339::InvalidFormat") {};
|
||||
InvalidFormat(const string &what) : invalid_argument(what) {};
|
||||
};
|
||||
|
||||
/*
|
||||
* This is basically a clone of habitat.utils.rfc3339, except all timestamps
|
||||
* are time_t (may have restrictions on 32 bit, and they cannot be floats
|
||||
* like they can in python)
|
||||
*
|
||||
* You should call tzset() before using either _localoffset function, since it
|
||||
* calls localtime_r().
|
||||
*/
|
||||
|
||||
bool validate_rfc3339(const string &rfc3339);
|
||||
long long int rfc3339_to_timestamp(const string &rfc3339);
|
||||
string timestamp_to_rfc3339_utcoffset(long long int timestamp);
|
||||
string timestamp_to_rfc3339_localoffset(long long int timestamp);
|
||||
string now_to_rfc3339_utcoffset();
|
||||
string now_to_rfc3339_localoffset();
|
||||
|
||||
} /* namespace RFC3339 */
|
||||
|
||||
#endif /* HABITAT_RFC3339_H */
|
|
@ -1,2 +1,7 @@
|
|||
test_extractor.pyc
|
||||
test_uploader.pyc
|
||||
test_rfc3339.pyc
|
||||
extractor
|
||||
cpp_connector
|
||||
cpp_connector_threaded
|
||||
rfc3339
|
||||
|
|
|
@ -0,0 +1,276 @@
|
|||
# Copyright 2012 (C) Adam Greig, Daniel Richman
|
||||
#
|
||||
# This file is part of habitat.
|
||||
#
|
||||
# habitat is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# habitat is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with habitat. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
import json
|
||||
from nose import SkipTest
|
||||
|
||||
# Identical to habitat's rfc3339 tests, except with a class that proxies
|
||||
# calls to an executable and without the float tests.
|
||||
|
||||
class ProxyFunction(object):
|
||||
def __init__(self, process, name):
|
||||
self.p = process
|
||||
self.name = name
|
||||
|
||||
def __call__(self, *args):
|
||||
args = [self.name] + list(args)
|
||||
self.p.stdin.write(json.dumps(args))
|
||||
self.p.stdin.write("\n")
|
||||
return json.loads(self.p.stdout.readline())
|
||||
|
||||
|
||||
class ProxyFunctionModule(object):
|
||||
# i.e., a module that contains functions only
|
||||
def __init__(self, command, environ=None):
|
||||
self.closed = False
|
||||
self.p = subprocess.Popen(command, env=environ,
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
|
||||
for n in ["validate_rfc3339", "rfc3339_to_timestamp",
|
||||
"timestamp_to_rfc3339_utcoffset",
|
||||
"timestamp_to_rfc3339_localoffset",
|
||||
"now_to_rfc3339_utcoffset",
|
||||
"now_to_rfc3339_localoffset"]:
|
||||
setattr(self, n, ProxyFunction(self.p, n))
|
||||
|
||||
def close(self, quiet=False):
|
||||
self.p.stdin.close()
|
||||
ret = self.p.wait()
|
||||
|
||||
if not quiet:
|
||||
assert ret == 0
|
||||
|
||||
def __del__(self):
|
||||
if not self.closed:
|
||||
self.close(True)
|
||||
|
||||
|
||||
class TestValidateRFC3339(object):
|
||||
def setup(self):
|
||||
self.mod = ProxyFunctionModule("tests/rfc3339")
|
||||
self.validate = self.mod.validate_rfc3339
|
||||
|
||||
def teardown(self):
|
||||
self.mod.close()
|
||||
|
||||
def test_rejects_bad_format(self):
|
||||
assert self.validate("asdf") == False
|
||||
assert self.validate("24822") == False
|
||||
assert self.validate("123-345-124T123:453:213") == False
|
||||
assert self.validate("99-09-12T12:42:21Z") == False
|
||||
assert self.validate("99-09-12T12:42:21+00:") == False
|
||||
assert self.validate("99-09-12T12:42:21+00:00") == False
|
||||
assert self.validate("2012-09-12T21:-1:21") == False
|
||||
|
||||
def test_rejects_no_offset(self):
|
||||
assert self.validate("2012-09-12T12:42:21") == False
|
||||
|
||||
def test_rejects_out_of_range(self):
|
||||
assert self.validate("2012-00-12T12:42:21Z") == False
|
||||
assert self.validate("2012-13-12T12:42:21Z") == False
|
||||
assert self.validate("2012-09-00T12:42:21Z") == False
|
||||
assert self.validate("2012-09-31T12:42:21Z") == False # Sep
|
||||
assert self.validate("2012-08-31T12:42:21Z") == True # Aug
|
||||
assert self.validate("2012-08-32T12:42:21Z") == False
|
||||
assert self.validate("2012-09-12T24:00:00Z") == False
|
||||
assert self.validate("2012-09-12T12:60:21Z") == False
|
||||
assert self.validate("2012-09-12T12:42:99Z") == False
|
||||
assert self.validate("2012-09-12T12:42:21+24:00") == False
|
||||
assert self.validate("2012-09-12T12:42:21-24:00") == False
|
||||
assert self.validate("2012-09-12T12:42:21+02:60") == False
|
||||
|
||||
def test_rejects_leap_seconds(self):
|
||||
# with regret :-(
|
||||
assert self.validate("2012-06-30T23:59:60Z") == False
|
||||
assert self.validate("2012-03-21T09:21:60Z") == False
|
||||
|
||||
def test_handles_leapyear(self):
|
||||
assert self.validate("2012-02-29T12:42:21Z") == True
|
||||
assert self.validate("2012-02-30T12:42:21Z") == False
|
||||
assert self.validate("2000-02-29T12:42:21Z") == True
|
||||
assert self.validate("2000-02-30T12:42:21Z") == False
|
||||
assert self.validate("2100-02-28T12:42:21Z") == True
|
||||
assert self.validate("2100-02-29T12:42:21Z") == False
|
||||
assert self.validate("2011-02-28T12:42:21Z") == True
|
||||
assert self.validate("2011-02-29T12:42:21Z") == False
|
||||
|
||||
def test_accepts_good(self):
|
||||
assert self.validate("1994-03-14T17:00:00Z") == True
|
||||
assert self.validate("2011-06-23T17:12:00+05:21") == True
|
||||
assert self.validate("1992-03-14T17:04:00-01:42") == True
|
||||
|
||||
|
||||
class TestRFC3339toTimestamp(object):
|
||||
def setup(self):
|
||||
self.mod = ProxyFunctionModule("tests/rfc3339")
|
||||
self.func = self.mod.rfc3339_to_timestamp
|
||||
|
||||
def teardown(self):
|
||||
self.mod.close()
|
||||
|
||||
def test_simple_cases(self):
|
||||
assert self.func("1996-12-19T16:39:57-08:00") == 851042397
|
||||
assert self.func("2012-08-08T21:30:36+01:00") == 1344457836
|
||||
assert self.func("1994-03-14T17:00:00Z") == 763664400
|
||||
assert self.func("1970-01-01T00:00:00Z") == 0
|
||||
assert self.func("1970-01-01T01:20:34Z") == 4834
|
||||
assert self.func("1969-12-31T23:59:59Z") == -1
|
||||
assert self.func("1969-12-31T22:51:12Z") == -4128
|
||||
|
||||
def test_y2038(self):
|
||||
assert self.func("2100-01-01T00:00:00Z") == 4102444800
|
||||
assert self.func("1900-01-01T00:00:00Z") == -2208988800
|
||||
|
||||
def test_leap_year(self):
|
||||
assert self.func("2012-02-29T12:42:21Z") == 1330519341
|
||||
assert self.func("2000-02-29T23:59:59Z") == 951868799
|
||||
assert self.func("2011-02-28T04:02:12Z") == 1298865732
|
||||
assert self.func("2100-02-28T00:00:00Z") == 4107456000
|
||||
assert self.func("1900-02-28T00:00:00Z") == -2203977600
|
||||
|
||||
def test_dst_transition(self):
|
||||
assert self.func("2012-03-25T00:59:59+00:00") == 1332637199
|
||||
assert self.func("2012-03-25T02:00:00+01:00") == 1332637200
|
||||
assert self.func("2012-10-28T01:00:00+01:00") == 1351382400
|
||||
assert self.func("2012-10-28T01:00:00+00:00") == 1351386000
|
||||
|
||||
def test_wacky_offset(self):
|
||||
assert self.func("1996-12-19T16:39:57-02:52") == 851042397 - 18480
|
||||
assert self.func("2012-08-08T21:30:36+23:11") == 1344457836 - 79860
|
||||
|
||||
def test_float(self):
|
||||
assert self.func("1996-12-19T16:39:57.1234-08:00") == 851042397
|
||||
assert self.func("1996-12-20T00:39:57.004Z") == 851042397
|
||||
|
||||
|
||||
class TestTimestampToRFC3339UTCOffset(object):
|
||||
def setup(self):
|
||||
self.mod = ProxyFunctionModule("tests/rfc3339")
|
||||
self.func = self.mod.timestamp_to_rfc3339_utcoffset
|
||||
|
||||
def teardown(self):
|
||||
self.mod.close()
|
||||
def test_simple_cases(self):
|
||||
assert self.func(851042397) == "1996-12-20T00:39:57Z"
|
||||
assert self.func(1344457836) == "2012-08-08T20:30:36Z"
|
||||
assert self.func(763664400) == "1994-03-14T17:00:00Z"
|
||||
assert self.func(0) == "1970-01-01T00:00:00Z"
|
||||
assert self.func(4834) == "1970-01-01T01:20:34Z"
|
||||
assert self.func(-1) == "1969-12-31T23:59:59Z"
|
||||
assert self.func(-4128) == "1969-12-31T22:51:12Z"
|
||||
|
||||
def test_y2038(self):
|
||||
try:
|
||||
assert self.func(4102444800) == "2100-01-01T00:00:00Z"
|
||||
assert self.func(-2208988800) == "1900-01-01T00:00:00Z"
|
||||
except ValueError as e:
|
||||
if str(e) == "timestamp out of range for platform time_t":
|
||||
raise SkipTest("Can't run this test on 32 bit")
|
||||
else:
|
||||
raise
|
||||
|
||||
def test_leap_year(self):
|
||||
assert self.func(1330519341) == "2012-02-29T12:42:21Z"
|
||||
assert self.func(951868799) == "2000-02-29T23:59:59Z"
|
||||
assert self.func(1298865732) == "2011-02-28T04:02:12Z"
|
||||
|
||||
try:
|
||||
assert self.func(4107456000) == "2100-02-28T00:00:00Z"
|
||||
assert self.func(-2203977600) == "1900-02-28T00:00:00Z"
|
||||
except ValueError as e:
|
||||
if str(e) == "timestamp out of range for platform time_t":
|
||||
raise SkipTest("Can't run this test on 32 bit")
|
||||
else:
|
||||
raise
|
||||
|
||||
def test_now(self):
|
||||
s = self.mod.now_to_rfc3339_utcoffset()
|
||||
assert s[-1] == "Z"
|
||||
assert len(s) == 20
|
||||
d = int(time.time()) - self.mod.rfc3339_to_timestamp(s)
|
||||
assert d == 0 or d == 1
|
||||
|
||||
|
||||
class TestTimestampToRFC3339LocalOffsetLondon(object):
|
||||
def setup(self):
|
||||
environ = os.environ.copy()
|
||||
environ["TZ"] = "Europe/London"
|
||||
|
||||
self.mod = ProxyFunctionModule("tests/rfc3339", environ=environ)
|
||||
self.func = self.mod.timestamp_to_rfc3339_localoffset
|
||||
|
||||
def teardown(self):
|
||||
self.mod.close()
|
||||
|
||||
def test_simple_cases(self):
|
||||
assert self.func(851042397) == "1996-12-20T00:39:57+00:00"
|
||||
assert self.func(1344457836) == "2012-08-08T21:30:36+01:00"
|
||||
assert self.func(763664400) == "1994-03-14T17:00:00+00:00"
|
||||
assert self.func(1340280000) == "2012-06-21T13:00:00+01:00"
|
||||
assert self.func(1234) == "1970-01-01T01:20:34+01:00"
|
||||
assert self.func(-7728) == "1969-12-31T22:51:12+01:00"
|
||||
|
||||
def test_dst_transition(self):
|
||||
assert self.func(1332637199) == "2012-03-25T00:59:59+00:00"
|
||||
assert self.func(1332637200) == "2012-03-25T02:00:00+01:00"
|
||||
assert self.func(1351382400) == "2012-10-28T01:00:00+01:00"
|
||||
assert self.func(1351386000) == "2012-10-28T01:00:00+00:00"
|
||||
|
||||
def test_now(self):
|
||||
s = self.mod.now_to_rfc3339_localoffset()
|
||||
w = self.mod.rfc3339_to_timestamp(s)
|
||||
assert s[-6:] == ["+00:00", "+01:00"][time.localtime(w).tm_isdst]
|
||||
|
||||
d = int(time.time()) - w
|
||||
assert d == 0 or d == 1
|
||||
|
||||
|
||||
class TestTimestampToRFC3339LocalOffsetNewYork(object):
|
||||
def setup(self):
|
||||
environ = os.environ.copy()
|
||||
environ["TZ"] = "America/New_York"
|
||||
|
||||
self.mod = ProxyFunctionModule("tests/rfc3339", environ=environ)
|
||||
self.func = self.mod.timestamp_to_rfc3339_localoffset
|
||||
|
||||
def teardown(self):
|
||||
self.mod.close()
|
||||
|
||||
def test_simple_cases(self):
|
||||
assert self.func(851042397) == "1996-12-19T19:39:57-05:00"
|
||||
assert self.func(1344457836) == "2012-08-08T16:30:36-04:00"
|
||||
assert self.func(761245200) == "1994-02-14T12:00:00-05:00"
|
||||
assert self.func(1340280000) == "2012-06-21T08:00:00-04:00"
|
||||
assert self.func(19234) == "1970-01-01T00:20:34-05:00"
|
||||
assert self.func(-4128) == "1969-12-31T17:51:12-05:00"
|
||||
|
||||
def test_dst_transition(self):
|
||||
assert self.func(1331449199) == "2012-03-11T01:59:59-05:00"
|
||||
assert self.func(1331449200) == "2012-03-11T03:00:00-04:00"
|
||||
assert self.func(1352005200) == "2012-11-04T01:00:00-04:00"
|
||||
assert self.func(1352008800) == "2012-11-04T01:00:00-05:00"
|
||||
|
||||
def test_now(self):
|
||||
s = self.mod.now_to_rfc3339_localoffset()
|
||||
w = self.mod.rfc3339_to_timestamp(s)
|
||||
assert s[-6:] == ["-05:00", "-04:00"][time.localtime(w).tm_isdst]
|
||||
|
||||
d = int(time.time()) - w
|
||||
assert d == 0 or d == 1
|
|
@ -0,0 +1,75 @@
|
|||
/* Copyright 2012 (C) Daniel Richman. License: GNU GPL 3; see LICENSE. */
|
||||
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <json/json.h>
|
||||
#include <ctime>
|
||||
|
||||
#include "RFC3339.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void handle_command(const Json::Value &command);
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
tzset();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
char line[1024];
|
||||
cin.getline(line, 1024);
|
||||
|
||||
if (line[0] == '\0')
|
||||
break;
|
||||
|
||||
Json::Reader reader;
|
||||
Json::Value command;
|
||||
|
||||
if (!reader.parse(line, command, false))
|
||||
throw runtime_error("JSON parsing failed");
|
||||
|
||||
if (!command.isArray() || !command[0u].isString())
|
||||
throw runtime_error("Invalid JSON input");
|
||||
|
||||
handle_command(command);
|
||||
}
|
||||
}
|
||||
|
||||
void reply(const Json::Value &what)
|
||||
{
|
||||
Json::FastWriter writer;
|
||||
cout << writer.write(what);
|
||||
}
|
||||
|
||||
void handle_command(const Json::Value &command)
|
||||
{
|
||||
string command_name = command[0u].asString();
|
||||
string string_arg;
|
||||
long long int int_arg = 0;
|
||||
|
||||
if (command_name == "validate_rfc3339" ||
|
||||
command_name == "rfc3339_to_timestamp")
|
||||
{
|
||||
string_arg = command[1u].asString();
|
||||
}
|
||||
else
|
||||
{
|
||||
int_arg = command[1u].asLargestInt();
|
||||
}
|
||||
|
||||
if (command_name == "validate_rfc3339")
|
||||
reply(RFC3339::validate_rfc3339(string_arg));
|
||||
else if (command_name == "rfc3339_to_timestamp")
|
||||
reply(RFC3339::rfc3339_to_timestamp(string_arg));
|
||||
else if (command_name == "timestamp_to_rfc3339_utcoffset")
|
||||
reply(RFC3339::timestamp_to_rfc3339_utcoffset(int_arg));
|
||||
else if (command_name == "timestamp_to_rfc3339_localoffset")
|
||||
reply(RFC3339::timestamp_to_rfc3339_localoffset(int_arg));
|
||||
else if (command_name == "now_to_rfc3339_utcoffset")
|
||||
reply(RFC3339::now_to_rfc3339_utcoffset());
|
||||
else if (command_name == "now_to_rfc3339_localoffset")
|
||||
reply(RFC3339::now_to_rfc3339_localoffset());
|
||||
else
|
||||
throw runtime_error("Command not found");
|
||||
}
|
Ładowanie…
Reference in New Issue