Skip to content

Commit dbf58e6

Browse files
chealdenebo
authored andcommitted
Fix #localtime and #gmtime when they are given numeric offsets
Both methods now use a common arg parser to determine the time zone from the passed IRubyObject. Additionally, #localtime always modifies the receiver now per the documentation, rather than returning a new object if an offset was passed. Conflicts: core/src/main/java/org/jruby/RubyTime.java [Addendum: Noticed some mistakes in original commit so I changed exactNum a bit]
1 parent b110106 commit dbf58e6

File tree

2 files changed

+77
-40
lines changed

2 files changed

+77
-40
lines changed

core/src/main/java/org/jruby/RubyTime.java

+76-37
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import java.util.regex.Matcher;
4848
import java.util.regex.Pattern;
4949

50+
import javax.print.DocFlavor;
5051
import org.joda.time.DateTime;
5152
import org.joda.time.DateTimeZone;
5253
import org.joda.time.IllegalFieldValueException;
@@ -68,6 +69,7 @@
6869

6970
import static org.jruby.CompatVersion.*;
7071
import org.jruby.runtime.Helpers;
72+
import org.jruby.util.TypeConverter;
7173

7274
import static org.jruby.runtime.Helpers.invokedynamic;
7375
import static org.jruby.runtime.invokedynamic.MethodNames.OP_CMP;
@@ -227,23 +229,68 @@ private static DateTimeZone parseZoneString(Ruby runtime, String zone) {
227229
return parseTZString(runtime, zone);
228230
}
229231

230-
public static DateTimeZone getTimeZoneFromUtcOffset(Ruby runtime, String utcOffset) {
231-
DateTimeZone cachedZone = runtime.getTimezoneCache().get(utcOffset);
232+
public static DateTimeZone getTimeZoneFromUtcOffset(Ruby runtime, IRubyObject utcOffset) {
233+
String strOffset = utcOffset.toString();
234+
235+
DateTimeZone cachedZone = runtime.getTimezoneCache().get(strOffset);
232236
if (cachedZone != null) return cachedZone;
233-
234-
Matcher offsetMatcher = TIME_OFFSET_PATTERN.matcher(utcOffset);
235-
if (!offsetMatcher.matches()) {
236-
throw runtime.newArgumentError("\"+HH:MM\" or \"-HH:MM\" expected for utc_offset");
237+
238+
DateTimeZone dtz;
239+
if(utcOffset instanceof RubyString) {
240+
Matcher offsetMatcher = TIME_OFFSET_PATTERN.matcher(strOffset);
241+
if (!offsetMatcher.matches()) {
242+
throw runtime.newArgumentError("\"+HH:MM\" or \"-HH:MM\" expected for utc_offset");
243+
}
244+
String sign = offsetMatcher.group(1);
245+
String hours = offsetMatcher.group(2);
246+
String minutes = offsetMatcher.group(3);
247+
dtz = getTimeZoneFromHHMM(runtime, "", !sign.equals("-"), hours, minutes);
248+
} else {
249+
IRubyObject numericOffset = numExact(runtime, utcOffset);
250+
int newOffset = (int)Math.round(numericOffset.convertToFloat().getDoubleValue() * 1000);
251+
dtz = getTimeZoneWithOffset(runtime, "", newOffset);
237252
}
238-
String sign = offsetMatcher.group(1);
239-
String hours = offsetMatcher.group(2);
240-
String minutes = offsetMatcher.group(3);
241-
DateTimeZone dtz = getTimeZoneFromHHMM(runtime, "", !sign.equals("-"), hours, minutes);
242253

243-
runtime.getTimezoneCache().put(utcOffset, dtz);
254+
runtime.getTimezoneCache().put(strOffset, dtz);
244255
return dtz;
245256
}
246257

258+
// mri: time.c num_exact
259+
private static IRubyObject numExact(Ruby runtime, IRubyObject v) {
260+
IRubyObject tmp;
261+
if (v instanceof RubyFixnum || v instanceof RubyBignum) return v;
262+
if (v.isNil()) exactTypeError(runtime, v);
263+
if (!(v instanceof RubyRational)) { // Default unknown
264+
if (v.respondsTo("to_r")) {
265+
tmp = v.callMethod(runtime.getCurrentContext(), "to_r");
266+
// WTF is this condition for? It responds to to_r and makes something which thinks it is a String?
267+
if (tmp != null && v.respondsTo("to_str")) exactTypeError(runtime, v);
268+
} else {
269+
tmp = TypeConverter.checkIntegerType(runtime, v, "to_int");
270+
if (tmp.isNil()) exactTypeError(runtime, v);
271+
}
272+
v = tmp;
273+
}
274+
275+
if (v instanceof RubyFixnum || v instanceof RubyBignum) {
276+
return v;
277+
} else if (v instanceof RubyRational) {
278+
RubyRational r = (RubyRational) v;
279+
if (r.denominator(runtime.getCurrentContext()) == RubyFixnum.newFixnum(runtime, 1)) {
280+
return r.numerator(runtime.getCurrentContext());
281+
}
282+
} else {
283+
exactTypeError(runtime, v);
284+
}
285+
286+
return v;
287+
}
288+
289+
private static void exactTypeError(Ruby runtime, IRubyObject received) {
290+
throw runtime.newTypeError(
291+
String.format("Can't convert %s into an exact number", received.getMetaClass().getRealClass()));
292+
}
293+
247294
private static DateTimeZone getTimeZoneFromHHMM(Ruby runtime, String name, boolean positive, String hours, String minutes) {
248295
int h = Integer.parseInt(hours);
249296
int m = 0;
@@ -404,11 +451,14 @@ public RubyTime localtime() {
404451

405452
@JRubyMethod(name = "localtime", optional = 1, compat = RUBY1_9)
406453
public RubyTime localtime19(ThreadContext context, IRubyObject[] args) {
407-
if (args.length == 0) return localtime();
408-
409-
String offset = args[0].asJavaString();
410-
DateTimeZone dtz = getTimeZoneFromUtcOffset(context.runtime, offset);
411-
return newTime(context.runtime, dt.withZone(dtz), nsec);
454+
DateTimeZone newDtz;
455+
if (args.length == 0) {
456+
newDtz = getLocalTimeZone(context.runtime);
457+
} else {
458+
newDtz = getTimeZoneFromUtcOffset(context.runtime, args[0]);
459+
}
460+
dt = dt.withZone(newDtz);
461+
return this;
412462
}
413463

414464
@JRubyMethod(name = {"gmt?", "utc?", "gmtime?"})
@@ -431,18 +481,8 @@ public RubyTime getlocal19(ThreadContext context, IRubyObject[] args) {
431481
if (args.length == 0 || args[0].isNil()) {
432482
return newTime(getRuntime(), dt.withZone(getLocalTimeZone(getRuntime())), nsec);
433483
} else {
434-
Matcher tzMatcher = TIME_OFFSET_PATTERN.matcher(args[0].toString());
435-
int hours, minutes, millis = 0;
436-
if (tzMatcher.matches()) {
437-
hours = Integer.parseInt(tzMatcher.group(2));
438-
minutes = Integer.parseInt(tzMatcher.group(3));
439-
millis = (hours * 60 + minutes) * 60 * 1000;
440-
if (tzMatcher.group(1).equals("-"))
441-
millis = -millis;
442-
} else {
443-
throw getRuntime().newArgumentError("\"+HH:MM\" or \"-HH:MM\" expected for utc_offset");
444-
}
445-
return newTime(getRuntime(), dt.withZone(DateTimeZone.forOffsetMillis(millis)), nsec);
484+
DateTimeZone dtz = getTimeZoneFromUtcOffset(context.runtime, args[0]);
485+
return newTime(getRuntime(), dt.withZone(dtz), nsec);
446486
}
447487
}
448488

@@ -725,8 +765,8 @@ private IRubyObject inspectCommon(DateTimeFormatter formatter, DateTimeFormatter
725765
@JRubyMethod(name = "to_a")
726766
@Override
727767
public RubyArray to_a() {
728-
return getRuntime().newArrayNoCopy(new IRubyObject[] { sec(), min(), hour(), mday(), month(),
729-
year(), wday(), yday(), isdst(), zone() });
768+
return getRuntime().newArrayNoCopy(new IRubyObject[]{sec(), min(), hour(), mday(), month(),
769+
year(), wday(), yday(), isdst(), zone()});
730770
}
731771

732772
@JRubyMethod(name = "to_f")
@@ -804,7 +844,7 @@ public RubyInteger year() {
804844

805845
@JRubyMethod(name = "wday")
806846
public RubyInteger wday() {
807-
return getRuntime().newFixnum((dt.getDayOfWeek()%7));
847+
return getRuntime().newFixnum((dt.getDayOfWeek() % 7));
808848
}
809849

810850
@JRubyMethod(name = "yday")
@@ -826,8 +866,7 @@ public IRubyObject subsec() {
826866
@JRubyMethod(name = {"gmt_offset", "gmtoff", "utc_offset"})
827867
public RubyInteger gmt_offset() {
828868
int offset = dt.getZone().getOffset(dt.getMillis());
829-
830-
return getRuntime().newFixnum((int)(offset/1000));
869+
return getRuntime().newFixnum(offset / 1000);
831870
}
832871

833872
@JRubyMethod(name = {"isdst", "dst?"})
@@ -995,7 +1034,7 @@ public RubyTime round(ThreadContext context, IRubyObject[] args) {
9951034
public static IRubyObject s_new(IRubyObject recv, IRubyObject[] args, Block block) {
9961035
Ruby runtime = recv.getRuntime();
9971036
RubyTime time = new RubyTime(runtime, (RubyClass) recv, new DateTime(getLocalTimeZone(runtime)));
998-
time.callInit(args,block);
1037+
time.callInit(args, block);
9991038
return time;
10001039
}
10011040

@@ -1278,10 +1317,10 @@ private static RubyTime createTime(IRubyObject recv, IRubyObject[] args, boolean
12781317
dtz = DateTimeZone.UTC;
12791318
} else if (args.length == 10 && args[9] instanceof RubyString) {
12801319
if (utcOffset) {
1281-
dtz = getTimeZoneFromUtcOffset(runtime, ((RubyString) args[9]).toString());
1320+
dtz = getTimeZoneFromUtcOffset(runtime, args[9]);
12821321
setTzRelative = true;
12831322
} else {
1284-
dtz = getTimeZoneFromString(runtime, ((RubyString) args[9]).toString());
1323+
dtz = getTimeZoneFromString(runtime, args[9].toString());
12851324
}
12861325
} else if (args.length == 10 && args[9].respondsTo("to_int")) {
12871326
IRubyObject offsetInt = args[9].callMethod(runtime.getCurrentContext(), "to_int");
@@ -1291,7 +1330,7 @@ private static RubyTime createTime(IRubyObject recv, IRubyObject[] args, boolean
12911330
}
12921331

12931332
if (args.length == 10) {
1294-
if (args[8] instanceof RubyBoolean) isDst = ((RubyBoolean) args[8]).isTrue();
1333+
if (args[8] instanceof RubyBoolean) isDst = args[8].isTrue();
12951334

12961335
args = new IRubyObject[] { args[5], args[4], args[3], args[2], args[1], args[0], runtime.getNil() };
12971336
} else {
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
fails:Time#getlocal returns a Time with UTC offset specified as an Integer number of seconds
21
fails:Time#getlocal returns a Time with a UTC offset of the specified number of Rational seconds
2+
fails:Time#getlocal raises ArgumentError if the String argument is not in an ASCII-compatible encoding
33
fails:Time#getlocal raises ArgumentError if the argument represents a value less than or equal to -86400 seconds
44
fails:Time#getlocal raises ArgumentError if the argument represents a value greater than or equal to 86400 seconds
5-
fails:Time#getlocal with an argument that responds to #to_int coerces using #to_int
65
fails:Time#getlocal with an argument that responds to #to_r coerces using #to_r
76
fails:Time#getlocal with an argument that responds to #to_str coerces using #to_str
8-
fails:Time#getlocal raises ArgumentError if the String argument is not in an ASCII-compatible encoding

0 commit comments

Comments
 (0)