Skip to content

Commit

Permalink
Showing 5 changed files with 184 additions and 130 deletions.
109 changes: 70 additions & 39 deletions truffle/src/main/java/org/jruby/truffle/nodes/core/TimeNodes.java
Original file line number Diff line number Diff line change
@@ -11,27 +11,28 @@

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeChildren;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.source.SourceSection;
import org.jcodings.specific.UTF8Encoding;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.objects.AllocateObjectNode;
import org.jruby.truffle.nodes.objects.AllocateObjectNodeGen;
import org.jruby.truffle.nodes.rubinius.TimePrimitiveNodes;
import org.jruby.truffle.nodes.time.ReadTimeZoneNode;
import org.jruby.truffle.runtime.NotProvided;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.core.StringOperations;
import org.jruby.truffle.runtime.layouts.Layouts;

@CoreClass(name = "Time")
public abstract class TimeNodes {

private static final DateTime ZERO = new DateTime(0);

public static DateTime getDateTime(DynamicObject time) {
return Layouts.TIME.getDateTime(time);
}

// We need it to copy the internal data for a call to Kernel#clone.
@CoreMethod(names = "initialize_copy", required = 1)
public abstract static class InitializeCopyNode extends CoreMethodArrayArgumentsNode {
@@ -42,8 +43,10 @@ public InitializeCopyNode(RubyContext context, SourceSection sourceSection) {

@Specialization(guards = "isRubyTime(from)")
public Object initializeCopy(DynamicObject self, DynamicObject from) {
Layouts.TIME.setDateTime(self, getDateTime(from));
Layouts.TIME.setDateTime(self, Layouts.TIME.getDateTime(from));
Layouts.TIME.setNSec(self, Layouts.TIME.getNSec(from));
Layouts.TIME.setOffset(self, Layouts.TIME.getOffset(from));
Layouts.TIME.setRelativeOffset(self, Layouts.TIME.getRelativeOffset(from));
return self;
}

@@ -59,66 +62,94 @@ public InternalGMTNode(RubyContext context, SourceSection sourceSection) {

@Specialization
public boolean internalGMT(DynamicObject time) {
return Layouts.TIME.getOffset(time) == nil() &&
(getDateTime(time).getZone().equals(DateTimeZone.UTC) ||
getDateTime(time).getZone().getOffset(getDateTime(time).getMillis()) == 0);
return Layouts.TIME.getIsUtc(time);
}
}

// Not a core method, used to simulate Rubinius @is_gmt.
@NodeChildren({
@NodeChild(type = RubyNode.class, value = "self"),
@NodeChild(type = RubyNode.class, value = "isGMT")
})
public abstract static class InternalSetGMTNode extends CoreMethodNode {
// Not a core method, used to simulate Rubinius @offset.
@NodeChild(type = RubyNode.class, value = "self")
public abstract static class InternalOffsetNode extends CoreMethodNode {

public InternalSetGMTNode(RubyContext context, SourceSection sourceSection) {
public InternalOffsetNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

@TruffleBoundary
@Specialization
public boolean internalSetGMT(DynamicObject time, boolean isGMT) {
if (isGMT) {
Layouts.TIME.setDateTime(time, getDateTime(time).withZone(DateTimeZone.UTC));
public Object internalOffset(DynamicObject time) {
final Object offset = Layouts.TIME.getOffset(time);
if (offset == nil()) {
return Layouts.TIME.getDateTime(time).getZone().getOffset(Layouts.TIME.getDateTime(time).getMillis()) / 1_000;
} else {
// Do nothing I guess - we can't change it to another zone, as what zone would that be?
return offset;
}

return isGMT;
}
}

// Not a core method, used to simulate Rubinius @offset.
@NodeChild(type = RubyNode.class, value = "self")
public abstract static class InternalOffsetNode extends CoreMethodNode {
@CoreMethod(names = "localtime_internal", optional = 1)
public abstract static class LocalTimeNode extends CoreMethodArrayArgumentsNode {
@Child private ReadTimeZoneNode readTimeZoneNode;

public InternalOffsetNode(RubyContext context, SourceSection sourceSection) {
public LocalTimeNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
readTimeZoneNode = new ReadTimeZoneNode(context, sourceSection);
}

@Specialization
public Object internalOffset(DynamicObject time) {
return Layouts.TIME.getOffset(time);
public DynamicObject localtime(VirtualFrame frame, DynamicObject time, NotProvided offset) {
final DynamicObject zoneName = (DynamicObject) readTimeZoneNode.execute(frame);
final DateTimeZone dateTimeZone = TimePrimitiveNodes.TimeZoneParser.parse(this, StringOperations.getString(getContext(), zoneName));
final String shortZoneName = TimePrimitiveNodes.TimeZoneParser.getShortZoneName(time, dateTimeZone);
final DynamicObject zone = createString(StringOperations.encodeByteList(shortZoneName, UTF8Encoding.INSTANCE));
final DateTime dateTime = Layouts.TIME.getDateTime(time);

Layouts.TIME.setIsUtc(time, false);
Layouts.TIME.setRelativeOffset(time, false);
Layouts.TIME.setZone(time, zone);
Layouts.TIME.setDateTime(time, dateTime.withZone(dateTimeZone));

return time;
}

@Specialization
public DynamicObject localtime(DynamicObject time, long offset) {
final DateTime dateTime = Layouts.TIME.getDateTime(time);
final DateTimeZone zone = getDateTimeZone((int) offset);

Layouts.TIME.setIsUtc(time, false);
Layouts.TIME.setRelativeOffset(time, true);
Layouts.TIME.setZone(time, nil());
Layouts.TIME.setDateTime(time, dateTime.withZone(zone));

return time;
}

@TruffleBoundary
public DateTimeZone getDateTimeZone(int offset) {
return DateTimeZone.forOffsetMillis(offset * 1000);
}

}

// Not a core method, used to simulate Rubinius @offset.
@NodeChildren({
@NodeChild(type = RubyNode.class, value = "self"),
@NodeChild(type = RubyNode.class, value = "offset")
})
public abstract static class InternalSetOffsetNode extends CoreMethodNode {
@CoreMethod(names = "gmtime")
public abstract static class GmTimeNode extends CoreMethodArrayArgumentsNode {

public InternalSetOffsetNode(RubyContext context, SourceSection sourceSection) {
public GmTimeNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

@Specialization
public Object internalSetOffset(DynamicObject time, Object offset) {
Layouts.TIME.setOffset(time, offset);
return offset;
public DynamicObject localtime(DynamicObject time) {
final DateTime dateTime = Layouts.TIME.getDateTime(time);

Layouts.TIME.setIsUtc(time, true);
Layouts.TIME.setRelativeOffset(time, false);
Layouts.TIME.setZone(time, nil());
Layouts.TIME.setDateTime(time, dateTime.withZone(DateTimeZone.UTC));

return time;
}

}

@CoreMethod(names = "allocate", constructor = true)
@@ -133,7 +164,7 @@ public AllocateNode(RubyContext context, SourceSection sourceSection) {

@Specialization
public DynamicObject allocate(DynamicObject rubyClass) {
return allocateObjectNode.allocate(rubyClass, ZERO, getContext().getCoreLibrary().getNilObject(), false);
return allocateObjectNode.allocate(rubyClass, ZERO, 0, getContext().getCoreLibrary().getNilObject(), false, false);
}

}
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@
import org.joda.time.tz.FixedDateTimeZone;
import org.jruby.runtime.Helpers;
import org.jruby.truffle.nodes.RubyGuards;
import org.jruby.truffle.nodes.core.TimeNodes;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.objects.AllocateObjectNode;
import org.jruby.truffle.nodes.objects.AllocateObjectNodeGen;
import org.jruby.truffle.nodes.time.ReadTimeZoneNode;
@@ -33,6 +33,7 @@
import org.jruby.truffle.runtime.layouts.Layouts;
import org.jruby.util.RubyDateFormatter;

import java.sql.Time;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
@@ -58,13 +59,13 @@ public TimeSNowPrimitiveNode(RubyContext context, SourceSection sourceSection) {
@Specialization
public DynamicObject timeSNow(VirtualFrame frame, DynamicObject timeClass) {
// TODO CS 4-Mar-15 whenever we get time we have to convert lookup and time zone to a string and look it up - need to cache somehow...
return allocateObjectNode.allocate(timeClass, now((DynamicObject) readTimeZoneNode.execute(frame)), nil(), false);
return allocateObjectNode.allocate(timeClass, now((DynamicObject) readTimeZoneNode.execute(frame)), 0, nil(), nil(), false, false);
}

@TruffleBoundary
private DateTime now(DynamicObject timeZone) {
assert RubyGuards.isRubyString(timeZone);
return DateTime.now(org.jruby.RubyTime.getTimeZoneFromTZString(getContext().getRuntime(), timeZone.toString()));
return DateTime.now(TimeZoneParser.parse(this, StringOperations.getString(getContext(), timeZone)));
}

}
@@ -81,8 +82,14 @@ public TimeSDupPrimitiveNode(RubyContext context, SourceSection sourceSection) {

@Specialization
public DynamicObject timeSDup(DynamicObject other) {
return allocateObjectNode.allocate(Layouts.BASIC_OBJECT.getLogicalClass(other), TimeNodes.getDateTime(other),
Layouts.TIME.getOffset(other), Layouts.TIME.getRelativeOffset(other));
return allocateObjectNode.allocate(
Layouts.BASIC_OBJECT.getLogicalClass(other),
Layouts.TIME.getDateTime(other),
Layouts.TIME.getNSec(other),
Layouts.TIME.getZone(other),
Layouts.TIME.getOffset(other),
Layouts.TIME.getRelativeOffset(other),
Layouts.TIME.getIsUtc(other));
}

}
@@ -97,18 +104,24 @@ public TimeSSpecificPrimitiveNode(RubyContext context, SourceSection sourceSecti
readTimeZoneNode = new ReadTimeZoneNode(context, sourceSection);
}

@Specialization(guards = { "isUTC", "isNil(offset)" })
@Specialization(guards = { "isUTC" })
public DynamicObject timeSSpecificUTC(long seconds, int nanoseconds, boolean isUTC, Object offset) {
// TODO(CS): overflow checks needed?
final long milliseconds = getMillis(seconds, nanoseconds);
return Layouts.TIME.createTime(getContext().getCoreLibrary().getTimeFactory(), time(milliseconds), nil(), false);
return Layouts.TIME.createTime(getContext().getCoreLibrary().getTimeFactory(), time(milliseconds), nanoseconds % 1_000_000, nil(), nil(), false, isUTC);
}

@Specialization(guards = { "!isUTC", "isNil(offset)" })
public DynamicObject timeSSpecific(VirtualFrame frame, long seconds, int nanoseconds, boolean isUTC, Object offset) {
// TODO(CS): overflow checks needed?
final long milliseconds = getMillis(seconds, nanoseconds);
return Layouts.TIME.createTime(getContext().getCoreLibrary().getTimeFactory(), localtime(milliseconds, (DynamicObject) readTimeZoneNode.execute(frame)), offset, false);
return Layouts.TIME.createTime(getContext().getCoreLibrary().getTimeFactory(),
localtime(milliseconds, (DynamicObject) readTimeZoneNode.execute(frame)), nanoseconds % 1_000_000, nil(), offset, false, isUTC);
}

@Specialization(guards = { "!isUTC" })
public DynamicObject timeSSpecific(VirtualFrame frame, long seconds, int nanoseconds, boolean isUTC, long offset) {
final long milliseconds = getMillis(seconds, nanoseconds);
return Layouts.TIME.createTime(getContext().getCoreLibrary().getTimeFactory(),
localtime(milliseconds, (DynamicObject) readTimeZoneNode.execute(frame)), nanoseconds % 1_000_000, nil(), offset, false, isUTC);
}

private long getMillis(long seconds, int nanoseconds) {
@@ -127,9 +140,9 @@ private DateTime time(long milliseconds) {
}

@TruffleBoundary
private DateTime localtime(long milliseconds, DynamicObject timeZone) {
assert RubyGuards.isRubyString(timeZone);
return new DateTime(milliseconds, org.jruby.RubyTime.getTimeZoneFromTZString(getContext().getRuntime(), timeZone.toString()));
private DateTime localtime(long milliseconds, DynamicObject zoneName) {
assert RubyGuards.isRubyString(zoneName);
return new DateTime(milliseconds, TimeZoneParser.parse(this, StringOperations.getString(getContext(), zoneName)));
}

}
@@ -143,7 +156,7 @@ public TimeSecondsPrimitiveNode(RubyContext context, SourceSection sourceSection

@Specialization
public long timeSeconds(DynamicObject time) {
return TimeNodes.getDateTime(time).getMillis() / 1_000;
return Layouts.TIME.getDateTime(time).getMillis() / 1_000;
}

}
@@ -158,7 +171,7 @@ public TimeUSecondsPrimitiveNode(RubyContext context, SourceSection sourceSectio
@TruffleBoundary
@Specialization
public long timeUSeconds(DynamicObject time) {
return TimeNodes.getDateTime(time).getMillisOfSecond() * 1_000L;
return Layouts.TIME.getDateTime(time).getMillisOfSecond() * 1_000L + (Layouts.TIME.getNSec(time) / 1_000L);
}

}
@@ -175,13 +188,13 @@ public TimeDecomposePrimitiveNode(RubyContext context, SourceSection sourceSecti

@Specialization
public DynamicObject timeDecompose(VirtualFrame frame, DynamicObject time) {
final String envTimeZoneString = readTimeZoneNode.execute(frame).toString();
return decompose(time, envTimeZoneString);
final DynamicObject envTimeZone = (DynamicObject) readTimeZoneNode.execute(frame);
return decompose(time, envTimeZone);
}

@TruffleBoundary
private DynamicObject decompose(DynamicObject time, String envTimeZoneString) {
final DateTime dateTime = TimeNodes.getDateTime(time);
private DynamicObject decompose(DynamicObject time, DynamicObject envTimeZone) {
final DateTime dateTime = Layouts.TIME.getDateTime(time);
final int sec = dateTime.getSecondOfMinute();
final int min = dateTime.getMinuteOfHour();
final int hour = dateTime.getHourOfDay();
@@ -198,12 +211,18 @@ private DynamicObject decompose(DynamicObject time, String envTimeZoneString) {
final int yday = dateTime.getDayOfYear();
final boolean isdst = !dateTime.getZone().isStandardOffset(dateTime.getMillis());

String zoneString = org.jruby.RubyTime.getRubyTimeZoneName(envTimeZoneString, dateTime);
final String envTimeZoneString = StringOperations.getString(getContext(), envTimeZone);
String zoneString = TimeZoneParser.getShortZoneName(dateTime, TimeZoneParser.parse(this, envTimeZoneString));
final Object zone;
if (Layouts.TIME.getRelativeOffset(time)) {
zone = nil();
} else {
zone = createString(StringOperations.encodeByteList(zoneString, UTF8Encoding.INSTANCE));
final Object timeZone = Layouts.TIME.getZone(time);
if (timeZone == nil()) {
zone = createString(StringOperations.encodeByteList(zoneString, UTF8Encoding.INSTANCE));
} else {
zone = timeZone;
}
}

final Object[] decomposed = new Object[]{ sec, min, hour, day, month, year, wday, yday, isdst, zone };
@@ -223,8 +242,8 @@ public TimeStrftimePrimitiveNode(RubyContext context, SourceSection sourceSectio
@Specialization(guards = "isRubyString(format)")
public DynamicObject timeStrftime(DynamicObject time, DynamicObject format) {
final RubyDateFormatter rdf = getContext().getRuntime().getCurrentContext().getRubyDateFormatter();
// TODO CS 15-Feb-15 ok to just pass nanoseconds as 0?
return createString(rdf.formatToByteList(rdf.compilePattern(StringOperations.getByteList(format), false), TimeNodes.getDateTime(time), 0, null));
return createString(rdf.formatToByteList(rdf.compilePattern(StringOperations.getByteList(format), false),
Layouts.TIME.getDateTime(time), Layouts.TIME.getNSec(time), null));
}

}
@@ -244,11 +263,12 @@ public TimeSFromArrayPrimitiveNode(RubyContext context, SourceSection sourceSect
@Specialization
public DynamicObject timeSFromArray(VirtualFrame frame, DynamicObject timeClass, int sec, int min, int hour, int mday, int month, int year,
int nsec, int isdst, boolean fromutc, Object utcoffset) {
String tz = null;

DynamicObject envZon = null;
if (!fromutc && utcoffset == nil()) {
tz = readTimeZoneNode.execute(frame).toString();
envZon = (DynamicObject) readTimeZoneNode.execute(frame);
}
return buildTime(timeClass, sec, min, hour, mday, month, year, nsec, isdst, fromutc, utcoffset, tz);
return buildTime(timeClass, sec, min, hour, mday, month, year, nsec, isdst, fromutc, utcoffset, envZon);
}

@Specialization(guards = "!isInteger(sec) || !isInteger(nsec)")
@@ -259,7 +279,7 @@ public DynamicObject timeSFromArrayFallback(VirtualFrame frame, DynamicObject ti

@TruffleBoundary
private DynamicObject buildTime(DynamicObject timeClass, int sec, int min, int hour, int mday, int month, int year,
int nsec, int isdst, boolean fromutc, Object utcoffset, String tz) {
int nsec, int isdst, boolean fromutc, Object utcoffset, DynamicObject envZon) {
if (sec < 0 || sec > 59 ||
min < 0 || min > 59 ||
hour < 0 || hour > 23 ||
@@ -270,33 +290,41 @@ private DynamicObject buildTime(DynamicObject timeClass, int sec, int min, int h

final DateTimeZone zone;
final boolean relativeOffset;
DynamicObject zoneToStore;
if (fromutc) {
zone = DateTimeZone.UTC;
relativeOffset = false;
zoneToStore = nil();
} else if (utcoffset == nil()) {
zone = org.jruby.RubyTime.getTimeZoneFromTZString(getContext().getRuntime(), tz);
zone = TimeZoneParser.parse(this, StringOperations.getString(getContext(), envZon));
final String zoneName = TimeZoneParser.getShortZoneName(new DateTime(year, month, mday, hour, min, sec), zone);
zoneToStore = createString(StringOperations.encodeByteList(zoneName, UTF8Encoding.INSTANCE));
relativeOffset = false;
} else if (utcoffset instanceof Integer) {
zone = DateTimeZone.forOffsetMillis(((int) utcoffset) * 1_000);
relativeOffset = true;
zoneToStore = nil();
} else if (utcoffset instanceof Long) {
zone = DateTimeZone.forOffsetMillis((int) ((long) utcoffset) * 1_000);
relativeOffset = true;
zoneToStore = nil();
} else if (utcoffset instanceof DynamicObject) {
final int millis = cast(ruby("(offset * 1000).to_i", "offset", utcoffset));
zone = DateTimeZone.forOffsetMillis(millis);
relativeOffset = true;
zoneToStore = nil();
} else {
throw new UnsupportedOperationException(String.format("%s %s %s %s", isdst, fromutc, utcoffset, utcoffset.getClass()));
}


if (isdst == -1) {
final DateTime dateTime = new DateTime(year, month, mday, hour, min, sec, nsec / 1_000_000, zone);
return allocateObjectNode.allocate(timeClass, dateTime, utcoffset, relativeOffset);
return allocateObjectNode.allocate(timeClass, dateTime, nsec % 1_000_000, zoneToStore, utcoffset, relativeOffset, fromutc);
} else {
// TODO (pitr 26-Nov-2015): is this correct to create the DateTime without isdst application?
final DateTime dateTime = new DateTime(year, month, mday, hour, min, sec, nsec / 1_000_000, zone);
return allocateObjectNode.allocate(timeClass, dateTime, utcoffset, relativeOffset);
return allocateObjectNode.allocate(timeClass, dateTime, nsec % 1_000_000, zoneToStore, utcoffset, relativeOffset, fromutc);
}
}

@@ -322,7 +350,7 @@ public TimeNSecondsPrimitiveNode(RubyContext context, SourceSection sourceSectio
@TruffleBoundary
@Specialization
public long timeNSeconds(DynamicObject time) {
return TimeNodes.getDateTime(time).getMillisOfSecond() * 1_000_000L;
return (Layouts.TIME.getDateTime(time).getMillisOfSecond() % 1000) * 1_000_000L + Layouts.TIME.getNSec(time);
}

}
@@ -337,43 +365,29 @@ public TimeSetNSecondsPrimitiveNode(RubyContext context, SourceSection sourceSec
@TruffleBoundary
@Specialization
public long timeSetNSeconds(DynamicObject time, int nanoseconds) {
Layouts.TIME.setDateTime(time, TimeNodes.getDateTime(time).withMillisOfSecond(nanoseconds / 1_000_000));
Layouts.TIME.setDateTime(time, Layouts.TIME.getDateTime(time).withMillisOfSecond(nanoseconds / 1_000_000));
Layouts.TIME.setNSec(time, nanoseconds % 1_000_000);
return nanoseconds;
}

}

// TODO (pitr 25-Nov-2015): clen up the implementation
@RubiniusPrimitive(name = "time_env_zone")
public static abstract class TimeEnvZonePrimitiveNode extends RubiniusPrimitiveNode {

@Child private ReadTimeZoneNode readTimeZoneNode;
@RubiniusPrimitive(name = "time_utc_offset")
public static abstract class TimeUTCOffsetPrimitiveNode extends RubiniusPrimitiveNode {

public TimeEnvZonePrimitiveNode(RubyContext context, SourceSection sourceSection) {
public TimeUTCOffsetPrimitiveNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
readTimeZoneNode = new ReadTimeZoneNode(context, sourceSection);
}

@TruffleBoundary
@Specialization
public Object timeEnvZone(VirtualFrame frame, DynamicObject time) {
final DynamicObject zoneName = (DynamicObject) readTimeZoneNode.execute(frame);

return createString(StringOperations.encodeByteList(getShortZoneName(time, zoneName), UTF8Encoding.INSTANCE));
public Object timeUTCOffset(DynamicObject time) {
return Layouts.TIME.getDateTime(time).getZone().getOffset(Layouts.TIME.getDateTime(time).getMillis()) / 1_000;
}

@TruffleBoundary
private String getShortZoneName(DynamicObject time, DynamicObject zoneName) {
DateTime dt = Layouts.TIME.getDateTime(time);
final DateTimeZone zone = parseTimeZoneString(StringOperations.getString(getContext(), zoneName));

dt = dt.withZone(zone);
Layouts.TIME.setDateTime(time, dt);
Layouts.TIME.setOffset(time, nil());
Layouts.TIME.setRelativeOffset(time, false);

return dt.getZone().getShortName(dt.getMillis());
}
}

public static class TimeZoneParser {
// Following private methods in this class were copied over from org.jruby.RubyTime.
// Slight modifications were made.

@@ -423,7 +437,18 @@ private String getShortZoneName(DynamicObject time, DynamicObject zoneName) {
"WET", "Europe/Lisbon" // Western European Time
);

private DateTimeZone parseTimeZoneString(String zone) {
public static String getShortZoneName(DynamicObject time, DateTimeZone zone) {
DateTime dateTime = Layouts.TIME.getDateTime(time);
return getShortZoneName(dateTime, zone);
}

@TruffleBoundary
public static String getShortZoneName(DateTime dateTime, DateTimeZone zone) {
return zone.getShortName(dateTime.getMillis());
}

@TruffleBoundary
public static DateTimeZone parse(RubyNode node, String zone) {
String upZone = zone.toUpperCase(Locale.ENGLISH);

Matcher tzMatcher = TZ_PATTERN.matcher(zone);
@@ -439,7 +464,7 @@ private DateTimeZone parseTimeZoneString(String zone) {
}

// Sign is reversed in legacy TZ notation
return getTimeZoneFromHHMM(zoneName, sign.equals("-"), hours, minutes, seconds);
return getTimeZoneFromHHMM(node, zoneName, sign.equals("-"), hours, minutes, seconds);
} else {
if (LONG_TZNAME.containsKey(upZone)) {
zone = LONG_TZNAME.get(upZone);
@@ -460,7 +485,12 @@ private DateTimeZone parseTimeZoneString(String zone) {
}
}

private DateTimeZone getTimeZoneFromHHMM(String name, boolean positive, String hours, String minutes, String seconds) {
private static DateTimeZone getTimeZoneFromHHMM(RubyNode node,
String name,
boolean positive,
String hours,
String minutes,
String seconds) {
int h = Integer.parseInt(hours);
int m = 0;
int s = 0;
@@ -473,14 +503,14 @@ private DateTimeZone getTimeZoneFromHHMM(String name, boolean positive, String h
}

if (h > 23 || m > 59) {
throw new RaiseException(getContext().getCoreLibrary().argumentError("utc_offset out of range", this));
throw new RaiseException(node.getContext().getCoreLibrary().argumentError("utc_offset out of range", node));
}

int offset = (positive ? +1 : -1) * ((h * 3600) + m * 60 + s) * 1000;
return timeZoneWithOffset(name, offset);
}

private DateTimeZone timeZoneWithOffset(String zoneName, int offset) {
private static DateTimeZone timeZoneWithOffset(String zoneName, int offset) {
if (zoneName.isEmpty()) {
return DateTimeZone.forOffsetMillis(offset);
} else {
@@ -490,25 +520,4 @@ private DateTimeZone timeZoneWithOffset(String zoneName, int offset) {

}

@RubiniusPrimitive(name = "time_utc_offset")
public static abstract class TimeUTCOffsetPrimitiveNode extends RubiniusPrimitiveNode {

public TimeUTCOffsetPrimitiveNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

@TruffleBoundary
@Specialization
public Object timeUTCOffset(DynamicObject time) {

Object offset = Layouts.TIME.getOffset(time);
if (offset != nil()) {
return offset;
} else {
return TimeNodes.getDateTime(time).getZone().getOffset(TimeNodes.getDateTime(time).getMillis()) / 1_000;
}
}

}

}
Original file line number Diff line number Diff line change
@@ -22,18 +22,30 @@ DynamicObjectFactory createTimeShape(DynamicObject logicalClass,

DynamicObject createTime(DynamicObjectFactory factory,
DateTime dateTime,
long nSec,
Object zone,
Object offset,
boolean relativeOffset);
boolean relativeOffset,
boolean isUtc);

boolean isTime(DynamicObject object);

DateTime getDateTime(DynamicObject object);
void setDateTime(DynamicObject object, DateTime value);

long getNSec(DynamicObject object);
void setNSec(DynamicObject object, long value);

Object getOffset(DynamicObject object);
void setOffset(DynamicObject object, Object value);

Object getZone(DynamicObject object);
void setZone(DynamicObject object, Object value);

boolean getRelativeOffset(DynamicObject object);
void setRelativeOffset(DynamicObject object, boolean value);

boolean getIsUtc(DynamicObject object);
void setIsUtc(DynamicObject object, boolean value);

}
Original file line number Diff line number Diff line change
@@ -1670,15 +1670,7 @@ public RubyNode visitInstAsgnNode(org.jruby.ast.InstAsgnNode node) {
final String path = getSourcePath(sourceSection);
final String corePath = context.getCoreLibrary().getCoreLoadPath() + "/core/";
final RubyNode ret;
if (path.equals(corePath + "rubinius/common/time.rb")) {
if (name.equals("@is_gmt")) {
ret = TimeNodesFactory.InternalSetGMTNodeFactory.create(context, sourceSection, self, rhs);
return addNewlineIfNeeded(node, ret);
} else if (name.equals("@offset")) {
ret = TimeNodesFactory.InternalSetOffsetNodeFactory.create(context, sourceSection, self, rhs);
return addNewlineIfNeeded(node, ret);
}
} else if (path.equals(corePath + "rubinius/common/hash.rb")) {
if (path.equals(corePath + "rubinius/common/hash.rb")) {
if (name.equals("@default")) {
ret = HashNodesFactory.SetDefaultValueNodeFactory.create(context, sourceSection, self, rhs);
return addNewlineIfNeeded(node, ret);
10 changes: 10 additions & 0 deletions truffle/src/main/ruby/core/rubinius/api/shims/time.rb
Original file line number Diff line number Diff line change
@@ -12,4 +12,14 @@ def to_f
seconds + nsec * 0.000000001 # Truffle: optimized
end

def localtime(offset = nil)
if offset
localtime_internal Rubinius::Type.coerce_to_utc_offset(offset)
else
localtime_internal
end

self
end

end

1 comment on commit 5683f8d

@eregon
Copy link
Member

@eregon eregon commented on 5683f8d Jan 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that nsec could be supported in DateTime with JodaOrg/joda-time#52 (comment).
It would require updating jruby if we want to keep the compatibility layer and avoid two libraries,
but computations with millis and external nanos are very error-prone at best.

Sorry, something went wrong.

Please sign in to comment.