Skip to content

Commit 3be91f8

Browse files
committed
give the 32/64bit supported time range detection another go
1 parent 9eac29d commit 3be91f8

File tree

2 files changed

+49
-36
lines changed

2 files changed

+49
-36
lines changed

ext/mysql2/result.c

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,40 @@
44
static rb_encoding *binaryEncoding;
55
#endif
66

7-
#if SIZEOF_INT < SIZEOF_LONG
8-
/**
9-
* on 64bit platforms we can handle dates way outside 2038-01-19T03:14:07
10-
* because of how I'm performing the math below, this will allow a maximum
11-
* timestamp of 9846-12-12T11:5999:59
12-
*/
13-
#define MYSQL2_MAX_YEAR 9999
7+
#if (SIZEOF_INT < SIZEOF_LONG) || defined(HAVE_RUBY_ENCODING_H)
8+
/* on 64bit platforms we can handle dates way outside 2038-01-19T03:14:07
9+
*
10+
* (9999*31557600) + (12*2592000) + (31*86400) + (11*3600) + (59*60) + 59
11+
*/
12+
#define MYSQL2_MAX_TIME 315578267999ULL
1413
#else
1514
/**
16-
* on 32bit platforms the maximum date the Time class can handle is 2038-01-19T03:14:07
17-
* 2082 = 2038+1+19+3+14+7
15+
* On 32bit platforms the maximum date the Time class can handle is 2038-01-19T03:14:07
16+
* 2038 years + 1 month + 19 days + 3 hours + 14 minutes + 7 seconds = 64318634047 seconds
17+
*
18+
* (2038*31557600) + (1*2592000) + (19*86400) + (3*3600) + (14*60) + 7
1819
*/
19-
#define MYSQL2_MAX_YEAR 2082
20+
#define MYSQL2_MAX_TIME 64318634047ULL
2021
#endif
2122

22-
#ifdef NEGATIVE_TIME_T
23-
/* 1901-12-13 20:45:52 UTC : The oldest time in 32-bit signed time_t. */
24-
#define MYSQL2_MIN_YEAR 1902
23+
#if defined(HAVE_RUBY_ENCODING_H)
24+
/* 0000-1-1 00:00:00 UTC
25+
*
26+
* (0*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 0
27+
*/
28+
#define MYSQL2_MIN_TIME 2678400ULL
29+
#elif defined(NEGATIVE_TIME_T)
30+
/* 1901-12-13 20:45:52 UTC : The oldest time in 32-bit signed time_t.
31+
*
32+
* (1901*31557600) + (12*2592000) + (13*86400) + (20*3600) + (45*60) + 52
33+
*/
34+
#define MYSQL2_MIN_TIME 60023299552ULL
2535
#else
26-
/* 1970-01-01 00:00:00 UTC : The Unix epoch - the oldest time in portable time_t. */
27-
#define MYSQL2_MIN_YEAR 1970
36+
/* 1970-01-01 00:00:01 UTC : The Unix epoch - the oldest time in portable time_t.
37+
*
38+
* (1970*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 1
39+
*/
40+
#define MYSQL2_MIN_TIME 62171150401ULL
2841
#endif
2942

3043
static VALUE cMysql2Result;
@@ -244,16 +257,20 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
244257
}
245258
case MYSQL_TYPE_TIMESTAMP: // TIMESTAMP field
246259
case MYSQL_TYPE_DATETIME: { // DATETIME field
247-
int year, month, day, hour, min, sec, tokens;
260+
unsigned int year, month, day, hour, min, sec, tokens;
261+
uint64_t seconds;
262+
248263
tokens = sscanf(row[i], "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
249-
if (year+month+day+hour+min+sec == 0) {
264+
seconds = (year*31557600ULL) + (month*2592000ULL) + (day*86400ULL) + (hour*3600ULL) + (min*60ULL) + sec;
265+
266+
if (seconds == 0) {
250267
val = Qnil;
251268
} else {
252269
if (month < 1 || day < 1) {
253270
rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
254271
val = Qnil;
255272
} else {
256-
if (year < MYSQL2_MIN_YEAR || year+month+day+hour+min+sec > MYSQL2_MAX_YEAR) { // use DateTime instead
273+
if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { // use DateTime instead
257274
VALUE offset = INT2NUM(0);
258275
if (db_timezone == intern_local) {
259276
offset = rb_funcall(cMysql2Client, intern_local_offset, 0);

spec/mysql2/result_spec.rb

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -181,35 +181,31 @@
181181
@test_result['date_time_test'].strftime("%F %T").should eql('2010-04-04 11:44:00')
182182
end
183183

184-
it "should return DateTime for a DATETIME value when outside the supported range, Time if otherwise" do
185-
if RUBY_PLATFORM =~ /mswin/
186-
inside_year = 1970
187-
outside_year = inside_year-1
184+
if 1.size == 4 # 32bit
185+
if RUBY_VERSION =~ /1.9/
186+
klass = Time
188187
else
189-
inside_year = 1902
190-
outside_year = inside_year-1
191-
end
192-
r = @client.query("SELECT CAST('#{inside_year}-1-1 01:01:01' AS DATETIME) as test")
193-
if defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /rbx/
194188
klass = DateTime
195-
else
196-
klass = Time
197189
end
198-
r.first['test'].class.should eql(klass)
199190

200-
r = @client.query("SELECT CAST('#{outside_year}-1-1 01:01:01' AS DATETIME) as test")
201-
r.first['test'].class.should eql(DateTime)
202-
end
191+
it "should return DateTime when timestamp is < 1901-12-13 20:45:52" do
192+
# 1901-12-13T20:45:52 is the min for 32bit Ruby 1.8
193+
r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test")
194+
r.first['test'].class.should eql(klass)
195+
end
203196

204-
if 1.size == 4 # 32bit
205197
it "should return DateTime when timestamp is > 2038-01-19T03:14:07" do
206198
# 2038-01-19T03:14:07 is the max for 32bit Ruby 1.8
207199
r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test")
208-
r.first['test'].class.should eql(DateTime)
200+
r.first['test'].class.should eql(klass)
209201
end
210202
elsif 1.size == 8 # 64bit
203+
it "should return Time when timestamp is < 1901-12-13 20:45:52" do
204+
r = @client.query("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test")
205+
r.first['test'].class.should eql(Time)
206+
end
207+
211208
it "should return Time when timestamp is > 2038-01-19T03:14:07" do
212-
# 2038-01-19 03:14:07 is the max for 32bit Ruby 1.8
213209
r = @client.query("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test")
214210
r.first['test'].class.should eql(Time)
215211
end

0 commit comments

Comments
 (0)