/*
 **** BEGIN LICENSE BLOCK *****
 * Version: EPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Eclipse Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/epl-v20.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2017-2018 The JRuby Team
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the EPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the EPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/

package org.jruby.ext.date;

import org.jcodings.specific.USASCIIEncoding;
import org.joda.time.DateTime;
import org.joda.time.DateTimeUtils;
import org.joda.time.DateTimeZone;
import org.joda.time.Chronology;
import org.joda.time.Instant;
import org.joda.time.chrono.GJChronology;
import org.joda.time.chrono.GregorianChronology;
import org.joda.time.chrono.JulianChronology;

import org.joni.Regex;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.java.proxies.JavaProxy;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.*;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.*;
import org.jruby.util.log.Logger;
import org.jruby.util.log.LoggerFactory;

import java.io.Serializable;
import java.time.*;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import static org.jruby.RubyRegexp.*;
import static org.jruby.ext.date.DateUtils.*;
import static org.jruby.util.Numeric.*;

JRuby's Date implementation - 'native' parts. In MRI, since 2.x, all of date.rb has been moved to native (C) code. NOTE: There's still date.rb, where this gets bootstrapped from.
Author:enebo, kares
Since:9.2
/** * JRuby's <code>Date</code> implementation - 'native' parts. * In MRI, since 2.x, all of date.rb has been moved to native (C) code. * * NOTE: There's still date.rb, where this gets bootstrapped from. * * @since 9.2 * * @author enebo * @author kares */
@JRubyClass(name = "Date") public class RubyDate extends RubyObject { static final Logger LOG = LoggerFactory.getLogger(RubyDate.class); static final GJChronology CHRONO_ITALY_UTC = GJChronology.getInstance(DateTimeZone.UTC); // The Julian Day Number of the Day of Calendar Reform for Italy // and the Catholic countries. static final int ITALY = 2299161; // 1582-10-15 // The Julian Day Number of the Day of Calendar Reform for England // and her Colonies. static final int ENGLAND = 2361222; // 1752-09-14 // A constant used to indicate that a Date should always use the Julian calendar. private static final double JULIAN_INFINITY = Double.POSITIVE_INFINITY; // Infinity.new static final long JULIAN = (long) JULIAN_INFINITY; // Infinity.new // A constant used to indicate that a Date should always use the Gregorian calendar. private static final double GREGORIAN_INFINITY = Double.NEGATIVE_INFINITY; // -Infinity.new static final long GREGORIAN = (long) GREGORIAN_INFINITY; // -Infinity.new static final int REFORM_BEGIN_YEAR = 1582; static final int REFORM_END_YEAR = 1930; protected RubyDate(Ruby runtime, RubyClass klass) { super(runtime, klass); } DateTime dt; int off; // @of (in seconds) long start = ITALY; // @sg long subMillisNum = 0, subMillisDen = 1; // @sub_millis static RubyClass createDateClass(Ruby runtime) { RubyClass Date = runtime.defineClass("Date", runtime.getObject(), ALLOCATOR); Date.setReifiedClass(RubyDate.class); Date.includeModule(runtime.getComparable()); Date.defineAnnotatedMethods(RubyDate.class); Date.setConstant("ITALY", runtime.newFixnum(ITALY)); Date.setConstant("ENGLAND", runtime.newFixnum(ENGLAND)); return Date; } // Julian Day Number day 0 ... `def self.civil(y=-4712, m=1, d=1, sg=ITALY)` static final DateTime defaultDateTime = new DateTime(-4712 - 1, 1, 1, 0, 0, CHRONO_ITALY_UTC); private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() { @Override public IRubyObject allocate(Ruby runtime, RubyClass klass) { return new RubyDate(runtime, klass, defaultDateTime); } }; static RubyClass getDate(final Ruby runtime) { return (RubyClass) runtime.getObject().getConstantAt("Date"); } static RubyClass getDateTime(final Ruby runtime) { return (RubyClass) runtime.getObject().getConstantAt("DateTime"); } public RubyDate(Ruby runtime, RubyClass klass, DateTime dt) { super(runtime, klass); this.dt = dt; // assuming of = 0 (UTC) } public RubyDate(Ruby runtime, DateTime dt) { this(runtime, getDate(runtime), dt); } RubyDate(Ruby runtime, RubyClass klass, DateTime dt, int off, long start) { super(runtime, klass); this.dt = dt; this.off = off; this.start = start; } RubyDate(Ruby runtime, RubyClass klass, DateTime dt, int off, long start, long subMillisNum, long subMillisDen) { super(runtime, klass); this.dt = dt; this.off = off; this.start = start; this.subMillisNum = subMillisNum; this.subMillisDen = subMillisDen; } public RubyDate(Ruby runtime, long millis, Chronology chronology) { super(runtime, getDate(runtime)); this.dt = new DateTime(millis, chronology); } RubyDate(ThreadContext context, RubyClass klass, IRubyObject ajd, Chronology chronology, int off) { this(context, klass, ajd, chronology, off, ITALY); } RubyDate(ThreadContext context, RubyClass klass, IRubyObject ajd, int off, long start) { this(context, klass, ajd, getChronology(context, start, off), off, start); } RubyDate(ThreadContext context, RubyClass klass, IRubyObject ajd, long[] rest, int off, long start) { this(context, klass, ajd, getChronology(context, start, off), off, start); if (rest[0] != 0) adjustWithDayFraction(context, this.dt, rest); } private RubyDate(ThreadContext context, RubyClass klass, IRubyObject ajd, Chronology chronology, int off, long start) { super(context.runtime, klass); this.dt = new DateTime(initMillis(context, ajd), chronology); this.off = off; this.start = start; } final RubyDate newInstance(final ThreadContext context, final DateTime dt, int off, long start) { return newInstance(context, dt, off, start, subMillisNum, subMillisDen); } // to be overriden by RubyDateTime RubyDate newInstance(final ThreadContext context, final DateTime dt, int off, long start, long subNum, long subDen) { return new RubyDate(context.runtime, getMetaClass(), dt, off, start, subNum, subDen); }
@notesince Date.new is a civil alias, this won't ever get used
Deprecated:kept due AR-JDBC (uses RubyClass.newInstance(...) to 'fast' allocate a Date instance)
/** * @note since <code>Date.new</code> is a <code>civil</code> alias, this won't ever get used * @deprecated kept due AR-JDBC (uses RubyClass.newInstance(...) to 'fast' allocate a Date instance) */
@JRubyMethod(visibility = Visibility.PRIVATE) public RubyDate initialize(ThreadContext context, IRubyObject dt) { this.dt = (DateTime) JavaUtil.unwrapJavaValue(dt); return this; } @JRubyMethod(visibility = Visibility.PRIVATE) public RubyDate initialize(ThreadContext context, IRubyObject ajd, IRubyObject of) { initialize(context, ajd, of, ITALY); return this; } @JRubyMethod(visibility = Visibility.PRIVATE) // used by marshal_load public RubyDate initialize(ThreadContext context, IRubyObject ajd, IRubyObject of, IRubyObject sg) { initialize(context, ajd, of, val2sg(context, sg)); return this; } private void initialize(final ThreadContext context, IRubyObject arg, IRubyObject of, final long start) { final int off = val2off(context, of); this.off = off; this.start = start; if (arg instanceof JavaProxy) { // backwards - compatibility with JRuby's date.rb this.dt = (DateTime) JavaUtil.unwrapJavaValue(arg); return; } this.dt = new DateTime(initMillis(context, arg), getChronology(context, start, off)); } static final int DAY_IN_SECONDS = 86_400; // 24 * 60 * 60 static final int DAY_MS = 86_400_000; // 24 * 60 * 60 * 1000 private RubyFixnum DAY_MS_CACHE; private long initMillis(final ThreadContext context, IRubyObject ajd) { final Ruby runtime = context.runtime; // cannot use DateTimeUtils.fromJulianDay since we need to keep ajd as a Rational for precision // millis, @sub_millis = ((ajd - UNIX_EPOCH_IN_AJD) * 86400000).divmod(1) IRubyObject val; if (ajd instanceof RubyFixnum) { val = ((RubyFixnum) ajd).op_minus(context, 4881175 / 2); val = ((RubyFixnum) val).op_mul(context, DAY_MS); val = ((RubyInteger) val).op_plus(context, RubyFixnum.newFixnum(runtime, DAY_MS / 2)); // missing 1/2 } else { RubyRational _UNIX_EPOCH_IN_AJD = RubyRational.newRational(runtime, -4881175, 2); // -(1970-01-01) val = _UNIX_EPOCH_IN_AJD.op_plus(context, ajd); val = DAY_MS(context).op_mul(context, val); } if (val instanceof RubyFixnum) { return ((RubyFixnum) val).getLongValue(); } // fallback val = ((RubyNumeric) val).divmod(context, RubyFixnum.one(context.runtime)); IRubyObject millis = ((RubyArray) val).eltInternal(0); if (!(millis instanceof RubyFixnum)) { // > java.lang.Long::MAX_VALUE throw runtime.newArgumentError("Date out of range: millis=" + millis + " (" + millis.getMetaClass() + ")"); } IRubyObject subMillis = ((RubyArray) val).eltInternal(1); this.subMillisNum = ((RubyNumeric) subMillis).numerator(context).convertToInteger().getLongValue(); this.subMillisDen = ((RubyNumeric) subMillis).denominator(context).convertToInteger().getLongValue(); return ((RubyFixnum) millis).getLongValue(); } private RubyFixnum DAY_MS(final ThreadContext context) { RubyFixnum v = DAY_MS_CACHE; if (v == null) v = DAY_MS_CACHE = context.runtime.newFixnum(DAY_MS); return v; } @Override public IRubyObject initialize_copy(IRubyObject original) { final RubyDate from = (RubyDate) original; this.dt = from.dt; this.off = from.off; this.start = from.start; this.subMillisNum = from.subMillisNum; this.subMillisDen = from.subMillisDen; return this; } // Date.new!(dt_or_ajd=0, of=0, sg=ITALY, sub_millis=0)
Deprecated:internal Date.new!
/** * @deprecated internal Date.new! */
@JRubyMethod(name = "new!", meta = true, visibility = Visibility.PRIVATE) public static RubyDate new_(ThreadContext context, IRubyObject self) { if (self == getDateTime(context.runtime)) { return new RubyDateTime(context.runtime, 0, CHRONO_ITALY_UTC); } return new RubyDate(context.runtime, 0, CHRONO_ITALY_UTC); }
Deprecated:internal Date.new!
/** * @deprecated internal Date.new! */
@JRubyMethod(name = "new!", meta = true, visibility = Visibility.PRIVATE) public static RubyDate new_(ThreadContext context, IRubyObject self, IRubyObject ajd) { if (ajd instanceof JavaProxy) { // backwards - compatibility with JRuby's date.rb if (self == getDateTime(context.runtime)) { return new RubyDateTime(context.runtime, (RubyClass) self, (DateTime) JavaUtil.unwrapJavaValue(ajd)); } return new RubyDate(context.runtime, (RubyClass) self, (DateTime) JavaUtil.unwrapJavaValue(ajd)); } if (self == getDateTime(context.runtime)) { return new RubyDateTime(context, (RubyClass) self, ajd, CHRONO_ITALY_UTC, 0); } return new RubyDate(context, (RubyClass) self, ajd, CHRONO_ITALY_UTC, 0); }
Deprecated:internal Date.new!
/** * @deprecated internal Date.new! */
@JRubyMethod(name = "new!", meta = true, visibility = Visibility.PRIVATE) public static RubyDate new_(ThreadContext context, IRubyObject self, IRubyObject ajd, IRubyObject of) { if (self == getDateTime(context.runtime)) { return new RubyDateTime(context.runtime, (RubyClass) self).initialize(context, ajd, of); } return new RubyDate(context.runtime, (RubyClass) self).initialize(context, ajd, of); }
Deprecated:internal Date.new!
/** * @deprecated internal Date.new! */
@JRubyMethod(name = "new!", meta = true, visibility = Visibility.PRIVATE) public static RubyDate new_(ThreadContext context, IRubyObject self, IRubyObject ajd, IRubyObject of, IRubyObject sg) { if (self == getDateTime(context.runtime)) { return new RubyDateTime(context.runtime, (RubyClass) self).initialize(context, ajd, of, sg); } return new RubyDate(context.runtime, (RubyClass) self).initialize(context, ajd, of, sg); }
# Create a new Date object for the Civil Date specified by # year +y+, month +m+, and day-of-month +d+. # # +m+ and +d+ can be negative, in which case they count # backwards from the end of the year and the end of the # month respectively. No wraparound is performed, however, # and invalid values cause an ArgumentError to be raised. # can be negative # # +y+ defaults to -4712, +m+ to 1, and +d+ to 1; this is # Julian Day Number day 0. # # +sg+ specifies the Day of Calendar Reform.
/** # Create a new Date object for the Civil Date specified by # year +y+, month +m+, and day-of-month +d+. # # +m+ and +d+ can be negative, in which case they count # backwards from the end of the year and the end of the # month respectively. No wraparound is performed, however, # and invalid values cause an ArgumentError to be raised. # can be negative # # +y+ defaults to -4712, +m+ to 1, and +d+ to 1; this is # Julian Day Number day 0. # # +sg+ specifies the Day of Calendar Reform. **/
// Date.civil([year=-4712[, month=1[, mday=1[, start=Date::ITALY]]]]) // Date.new([year=-4712[, month=1[, mday=1[, start=Date::ITALY]]]]) @JRubyMethod(name = "civil", alias = "new", meta = true) public static RubyDate civil(ThreadContext context, IRubyObject self) { return new RubyDate(context.runtime, (RubyClass) self, defaultDateTime); } @JRubyMethod(name = "civil", alias = "new", meta = true) public static RubyDate civil(ThreadContext context, IRubyObject self, IRubyObject year) { return new RubyDate(context.runtime, (RubyClass) self, civilImpl(context, year)); } static DateTime civilImpl(ThreadContext context, IRubyObject year) { int y = getYear(year); final DateTime dt; try { dt = defaultDateTime.withYear(y); } catch (IllegalArgumentException ex) { throw context.runtime.newArgumentError("invalid date"); } return dt; } @JRubyMethod(name = "civil", alias = "new", meta = true) public static RubyDate civil(ThreadContext context, IRubyObject self, IRubyObject year, IRubyObject month) { return new RubyDate(context.runtime, (RubyClass) self, civilImpl(context, year, month)); } static DateTime civilImpl(ThreadContext context, IRubyObject year, IRubyObject month) { int y = getYear(year); int m = getMonth(month); final DateTime dt; final Chronology chronology = defaultDateTime.getChronology(); long millis = defaultDateTime.getMillis(); try { millis = chronology.year().set(millis, y); millis = chronology.monthOfYear().set(millis, m); dt = defaultDateTime.withMillis(millis); } catch (IllegalArgumentException ex) { throw context.runtime.newArgumentError("invalid date"); } return dt; } @JRubyMethod(name = "civil", alias = "new", meta = true) public static RubyDate civil(ThreadContext context, IRubyObject self, IRubyObject year, IRubyObject month, IRubyObject mday) { // return civil(context, self, new IRubyObject[] { year, month, mday, RubyFixnum.newFixnum(context.runtime, ITALY) }); return civilImpl(context, (RubyClass) self, year, month, mday, ITALY); } private static RubyDate civilImpl(ThreadContext context, RubyClass klass, IRubyObject year, IRubyObject month, IRubyObject mday, final long sg) { final int y = (sg > 0) ? getYear(year) : year.convertToInteger().getIntValue(); final int m = getMonth(month); final long[] rest = new long[] { 0, 1 }; final int d = (int) RubyDateTime.getDay(context, mday, rest); DateTime dt = civilDate(context, y, m ,d, getChronology(context, sg, 0)); RubyDate date = new RubyDate(context.runtime, klass, dt, 0, sg); if (rest[0] != 0) date.adjustWithDayFraction(context, dt, rest); return date; } @JRubyMethod(name = "civil", alias = "new", meta = true, optional = 4) // 4 args case public static RubyDate civil(ThreadContext context, IRubyObject self, IRubyObject[] args) { // IRubyObject year, IRubyObject month, IRubyObject mday, IRubyObject start switch (args.length) { // NOTE: slightly annoying but send might route its Date.send(:civil, *args) here case 0: return civil(context, self); case 1: return civil(context, self, args[0]); case 2: return civil(context, self, args[0], args[1]); // besides the above note on send ^^ // interpreter does not have a ThreeOperandArgNoBlockCallInstr thus routes 3 args as args[] here case 3: return civil(context, self, args[0], args[1], args[2]); } final long sg = val2sg(context, args[3]); return civilImpl(context, (RubyClass) self, args[0], args[1], args[2], sg); } public static DateTime civilDate(ThreadContext context, final int y, final int m, final int d, final Chronology chronology) { DateTime dt; try { if (d >= 0) { // let d == 0 fail (raise 'invalid date') dt = new DateTime(y, m, d, 0, 0, chronology); } else { dt = new DateTime(y, m, 1, 0, 0, chronology); long ms = dt.getMillis(); int last = chronology.dayOfMonth().getMaximumValue(ms); ms = chronology.dayOfMonth().set(ms, last + d + 1); // d < 0 (d == -1 -> d == 31) dt = dt.withMillis(ms); } } catch (IllegalArgumentException ex) { debug(context, "invalid date", ex); throw context.runtime.newArgumentError("invalid date"); } return dt; } // NOTE: no Bignum special care since JODA does not support 'huge' years anyway static int getYear(IRubyObject year) { int y = year.convertToInteger().getIntValue(); // handles Rational(x, y) return (y <= 0) ? --y : y; // due julian date calc -> see adjustJodaYear } static int getMonth(IRubyObject month) { int m = month.convertToInteger().getIntValue(); // handles Rational(x, y) return (m < 0) ? m + 13 : m; } @JRubyMethod(name = "valid_civil?", alias = "valid_date?", meta = true, required = 3, optional = 1) public static IRubyObject valid_civil_p(ThreadContext context, IRubyObject self, IRubyObject[] args) { final long sg = args.length > 3 ? val2sg(context, args[3]) : ITALY; final Long jd = validCivilImpl(args[0], args[1], args[2], sg); return jd == null ? context.fals : context.tru; } static Long validCivilImpl(IRubyObject year, IRubyObject month, IRubyObject day, final long sg) { final int y = year.convertToInteger().getIntValue(); final int m = getMonth(month); final int d = day.convertToInteger().getIntValue(); return DateUtils._valid_civil_p(y, m, d, sg); } // Do hour +h+, minute +min+, and second +s+ constitute a valid time? // If they do, returns their value as a fraction of a day. If not, returns nil. @JRubyMethod(name = "_valid_time?", meta = true, visibility = Visibility.PRIVATE) public static IRubyObject _valid_time_p(ThreadContext context, IRubyObject self, IRubyObject h, IRubyObject m, IRubyObject s) { long hour = normIntValue(h, 24); long min = normIntValue(m, 60); long sec = normIntValue(s, 60); if (valid_time_p(hour, min, sec)) { return timeToDayFraction(context, (int) hour, (int) min, (int) sec); } return context.nil; } private static long normIntValue(IRubyObject val, final int negOffset) { long v; if (val instanceof RubyFixnum) { v = ((RubyFixnum) val).getLongValue(); } else { v = val.convertToInteger().getLongValue(); } return (v < 0) ? v + negOffset : v; } // Rational(h * 3600 + min * 60 + s, 86400) static RubyNumeric timeToDayFraction(ThreadContext context, int hour, int min, int sec) { return (RubyNumeric) RubyRational.newRationalCanonicalize(context, hour * 3600 + min * 60 + sec, DAY_IN_SECONDS); }
Create a new Date object from a Julian Day Number. +jd+ is the Julian Day Number; if not specified, it defaults to 0. +sg+ specifies the Day of Calendar Reform.
/** * Create a new Date object from a Julian Day Number. * * +jd+ is the Julian Day Number; if not specified, it defaults to 0. * +sg+ specifies the Day of Calendar Reform. */
@JRubyMethod(name = "jd", meta = true) public static RubyDate jd(ThreadContext context, IRubyObject self) { // jd = 0, sg = ITALY return new RubyDate(context.runtime, (RubyClass) self, defaultDateTime); } @JRubyMethod(name = "jd", meta = true) public static RubyDate jd(ThreadContext context, IRubyObject self, IRubyObject jd) { // sg = ITALY return jdImpl(context, self, jd, ITALY); } @JRubyMethod(name = "jd", meta = true) public static RubyDate jd(ThreadContext context, IRubyObject self, IRubyObject jd, IRubyObject sg) { return jdImpl(context, self, jd, val2sg(context, sg)); } private static RubyDate jdImpl(ThreadContext context, IRubyObject self, IRubyObject jd, final long sg) { final long[] rest = new long[] { 0, 1 }; long jdi = RubyDateTime.getDay(context, jd, rest); RubyNumeric ajd = jd_to_ajd(context, jdi); return new RubyDate(context, (RubyClass) self, ajd, rest, 0, sg); } private void adjustWithDayFraction(ThreadContext context, DateTime dt, final long[] rest) { final RubyFixnum zero = RubyFixnum.zero(context.runtime); int ival; ival = RubyDateTime.getHour(context, zero, rest); dt = dt.plusHours(ival); if (rest[0] != 0) { ival = RubyDateTime.getMinute(context, zero, rest); dt = dt.plusMinutes(ival); if (rest[0] != 0) { ival = RubyDateTime.getSecond(context, zero, rest); dt = dt.plusSeconds(ival); final long r0 = rest[0], r1 = rest[1]; if (r0 != 0) { long millis = ( 1000 * r0 ) / r1; dt = dt.plusMillis((int) millis); subMillisNum = ((1000 * r0) - (millis * r1)); subMillisDen = r1; normalizeSubMillis(); } } } this.dt = dt; } final RubyDate normalizeSubMillis() { long subNum = subMillisNum; long subDen = subMillisDen; if (subNum == 0) subMillisDen = 1; else { long gcd = i_gcd(subNum, subDen); subMillisNum = subNum / gcd; subMillisDen = subDen / gcd; } return this; } @JRubyMethod(name = "valid_jd?", meta = true) public static IRubyObject valid_jd_p(ThreadContext context, IRubyObject self, IRubyObject jd) { return jd == context.nil ? context.fals : context.tru; // @see _valid_jd_p } @JRubyMethod(name = "valid_jd?", meta = true) public static IRubyObject valid_jd_p(ThreadContext context, IRubyObject self, IRubyObject jd, IRubyObject sg) { return jd == context.nil ? context.fals : context.tru; // @see _valid_jd_p } // Is +jd+ a valid Julian Day Number? // // If it is, returns it. In fact, any value is treated as a valid Julian Day Number. @JRubyMethod(name = "_valid_jd?", meta = true, visibility = Visibility.PRIVATE) public static IRubyObject _valid_jd_p(IRubyObject self, IRubyObject jd) { return jd; } @JRubyMethod(name = "_valid_jd?", meta = true, visibility = Visibility.PRIVATE) public static IRubyObject _valid_jd_p(IRubyObject self, IRubyObject jd, IRubyObject sg) { return jd; } @JRubyMethod(name = "ordinal", meta = true, optional = 3) public static RubyDate ordinal(ThreadContext context, IRubyObject self, IRubyObject[] args) { // ordinal(y=-4712, d=1, sg=ITALY) final int len = args.length; final long sg = len > 2 ? val2sg(context, args[2]) : ITALY; IRubyObject year = (len > 0) ? args[0] : RubyFixnum.newFixnum(context.runtime, -4712); IRubyObject day = (len > 1) ? args[1] : RubyFixnum.newFixnum(context.runtime, 1); final long[] rest = new long[] { 0, 1 }; final int d = (int) RubyDateTime.getDay(context, day, rest); Long jd = validOrdinalImpl(year, d, sg); if (jd == null) { throw context.runtime.newArgumentError("invalid date"); } return new RubyDate(context, (RubyClass) self, jd_to_ajd(context, jd), rest, 0, sg); } @JRubyMethod(name = "valid_ordinal?", meta = true, required = 2, optional = 1) public static IRubyObject valid_ordinal_p(ThreadContext context, IRubyObject self, IRubyObject[] args) { final long sg = args.length > 2 ? val2sg(context, args[2]) : ITALY; final Long jd = validOrdinalImpl(args[0], args[1], sg); return jd == null ? context.fals : context.tru; } static Long validOrdinalImpl(IRubyObject year, IRubyObject day, final long sg) { return validOrdinalImpl(year, day.convertToInteger().getIntValue(), sg); } private static Long validOrdinalImpl(IRubyObject year, int day, final long sg) { final int y = year.convertToInteger().getIntValue(); return DateUtils._valid_ordinal_p(y, day, sg); } @Deprecated // NOTE: should go away once no date.rb is using it @JRubyMethod(name = "_valid_ordinal?", meta = true, required = 2, optional = 1, visibility = Visibility.PRIVATE) public static IRubyObject _valid_ordinal_p(ThreadContext context, IRubyObject self, IRubyObject[] args) { final long sg = args.length > 2 ? val2sg(context, args[2]) : GREGORIAN; final Long jd = validOrdinalImpl(args[0], args[1], sg); return jd == null ? context.nil : RubyFixnum.newFixnum(context.runtime, jd); } @Deprecated // NOTE: should go away once no date.rb is using it @JRubyMethod(name = "_valid_ordinal?", required = 2, optional = 1, visibility = Visibility.PRIVATE) public IRubyObject _valid_ordinal_p(ThreadContext context, IRubyObject[] args) { return RubyDate._valid_ordinal_p(context, null, args); } @JRubyMethod(name = "commercial", meta = true, optional = 4) public static RubyDate commercial(ThreadContext context, IRubyObject self, IRubyObject[] args) { // commercial(y=-4712, w=1, d=1, sg=ITALY) final int len = args.length; final long sg = len > 3 ? val2sg(context, args[3]) : ITALY; IRubyObject year = (len > 0) ? args[0] : RubyFixnum.newFixnum(context.runtime, -4712); IRubyObject week = (len > 1) ? args[1] : RubyFixnum.newFixnum(context.runtime, 1); IRubyObject day = (len > 2) ? args[2] : RubyFixnum.newFixnum(context.runtime, 1); Long jd = validCommercialImpl(year, week, day, sg); if (jd == null) { throw context.runtime.newArgumentError("invalid date"); } return new RubyDate(context, (RubyClass) self, jd_to_ajd(context, jd), 0, sg); } @JRubyMethod(name = "valid_commercial?", meta = true, required = 3, optional = 1) public static IRubyObject valid_commercial_p(ThreadContext context, IRubyObject self, IRubyObject[] args) { final long sg = args.length > 3 ? val2sg(context, args[3]) : ITALY; final Long jd = validCommercialImpl(args[0], args[1], args[2], sg); return jd == null ? context.fals : context.tru; } static Long validCommercialImpl(IRubyObject year, IRubyObject week, IRubyObject day, final long sg) { final int y = year.convertToInteger().getIntValue(); int w = week.convertToInteger().getIntValue(); int d = day.convertToInteger().getIntValue(); return DateUtils._valid_commercial_p(y, w, d, sg); } @Deprecated // NOTE: should go away once no date.rb is using it @JRubyMethod(name = "_valid_commercial?", meta = true, required = 3, optional = 1, visibility = Visibility.PRIVATE) public static IRubyObject _valid_commercial_p(ThreadContext context, IRubyObject self, IRubyObject[] args) { final long sg = args.length > 3 ? val2sg(context, args[3]) : GREGORIAN; final Long jd = validCommercialImpl(args[0], args[1], args[2], sg); return jd == null ? context.nil : RubyFixnum.newFixnum(context.runtime, jd); } @Deprecated // NOTE: should go away once no date.rb is using it @JRubyMethod(name = "_valid_weeknum?", meta = true, required = 4, optional = 1, visibility = Visibility.PRIVATE) public static IRubyObject _valid_weeknum_p(ThreadContext context, IRubyObject self, IRubyObject[] args) { final long sg = args.length > 4 ? val2sg(context, args[4]) : GREGORIAN; final int y = args[0].convertToInteger().getIntValue(); final int w = args[1].convertToInteger().getIntValue(); final int d = args[2].convertToInteger().getIntValue(); final int f = args[3].convertToInteger().getIntValue(); final Long jd = DateUtils._valid_weeknum_p(y, w, d, f, sg); return jd == null ? context.nil : RubyFixnum.newFixnum(context.runtime, jd); }
# Create a new Date object representing today. # # +sg+ specifies the Day of Calendar Reform.
/** # Create a new Date object representing today. # # +sg+ specifies the Day of Calendar Reform. **/
@JRubyMethod(meta = true) public static RubyDate today(ThreadContext context, IRubyObject self) { // sg=ITALY return new RubyDate(context.runtime, (RubyClass) self, todayDate(context, CHRONO_ITALY_UTC)); } @JRubyMethod(meta = true) public static RubyDate today(ThreadContext context, IRubyObject self, IRubyObject sg) { final long start = val2sg(context, sg); final Chronology chrono = getChronology(context, start, 0); return new RubyDate(context.runtime, (RubyClass) self, todayDate(context, chrono), 0, start); } private static DateTime todayDate(final ThreadContext context, final Chronology chrono) { org.joda.time.LocalDate today = new org.joda.time.LocalDate(RubyTime.getLocalTimeZone(context.runtime)); return new DateTime(today.getYear(), today.getMonthOfYear(), today.getDayOfMonth(), 0, 0, chrono); } @JRubyMethod(name = "_valid_civil?", meta = true, required = 3, optional = 1) public static IRubyObject _valid_civil_p(ThreadContext context, IRubyObject self, IRubyObject[] args) { final long sg = args.length > 3 ? val2sg(context, args[3]) : GREGORIAN; final Long jd = validCivilImpl(args[0], args[1], args[2], sg); return jd == null ? context.nil : RubyFixnum.newFixnum(context.runtime, jd); } @Deprecated // NOTE: should go away once no date.rb is using it @JRubyMethod(name = "_valid_civil?", required = 3, optional = 1, visibility = Visibility.PRIVATE) public IRubyObject _valid_civil_p(ThreadContext context, IRubyObject[] args) { return RubyDate._valid_civil_p(context, null, args); } public DateTime getDateTime() { return dt; } @Override public boolean equals(Object other) { if (other instanceof RubyDate) { return equals((RubyDate) other); } return false; } public final boolean equals(RubyDate that) { return this.start == that.start && this.dt.equals(that.dt) && this.subMillisNum == that.subMillisNum && this.subMillisDen == that.subMillisDen; } @Override @JRubyMethod(name = "eql?", required = 1) public IRubyObject eql_p(IRubyObject other) { if (other instanceof RubyDate) { return getRuntime().newBoolean( equals((RubyDate) other) ); } return getRuntime().getFalse(); }
The relationship operator for Date. Compares dates by Julian Day Number. When comparing two DateTime instances, or a DateTime with a Date, the instances will be regarded as equivalent if they fall on the same date in local time.
Params:
  • context –
  • other –
Returns:true/false/nil
/** * The relationship operator for Date. * * Compares dates by Julian Day Number. When comparing two DateTime instances, or a DateTime with a Date, * the instances will be regarded as equivalent if they fall on the same date in local time. * @param context * @param other * @return true/false/nil */
@Override @JRubyMethod(name = "===", required = 1) public IRubyObject op_eqq(ThreadContext context, IRubyObject other) { if (other instanceof RubyDate) { return f_equal(context, jd(context), ((RubyDate) other).jd(context)); } if (other instanceof RubyNumeric) { return f_equal(context, jd(context), other); } return fallback_eqq(context, other); // rb_num_coerce_cmp(self, other, "==") } private IRubyObject fallback_eqq(ThreadContext context, IRubyObject other) { RubyArray res; try { res = (RubyArray) other.callMethod(context, "coerce", this); } catch (RaiseException ex) { if (ex.getException() instanceof RubyNoMethodError) return context.nil; throw ex; } return f_equal(context, res.eltInternal(0), res.eltInternal(1)); } @Override @JRubyMethod(name = "<=>", required = 1) public IRubyObject op_cmp(ThreadContext context, IRubyObject other) { if (other instanceof RubyDate) { return context.runtime.newFixnum(cmp(context, (RubyDate) other)); } // other (Numeric) - interpreted as an Astronomical Julian Day Number. // Comparison is by Astronomical Julian Day Number, including // fractional days. This means that both the time and the // timezone offset are taken into account when comparing // two DateTime instances. When comparing a DateTime instance // with a Date instance, the time of the latter will be // considered as falling on midnight UTC. if (other instanceof RubyNumeric) { return f_cmp(context, ajd(context), other); } return fallback_cmp(context, other); } private int cmp(ThreadContext context, final RubyDate that) { int cmp = this.dt.compareTo(that.dt); // 0, +1, -1 if (cmp == 0) { if (this.subMillisDen == 1 && that.subMillisDen == 1) { long subNum1 = this.subMillisNum; long subNum2 = that.subMillisNum; if (subNum1 < subNum2) return -1; if (subNum1 > subNum2) return +1; return 0; } return cmpSubMillis(context, that); } return cmp; } private int cmpSubMillis(ThreadContext context, final RubyDate that) { RubyNumeric diff = subMillisDiff(context, that); return diff.isZero() ? 0 : ( Numeric.f_negative_p(context, diff) ? -1 : +1 ); } private IRubyObject fallback_cmp(ThreadContext context, IRubyObject other) { RubyArray res; try { res = (RubyArray) other.callMethod(context, "coerce", this); } catch (RaiseException ex) { if (ex.getException() instanceof RubyNoMethodError) return context.nil; throw ex; } return f_cmp(context, res.eltInternal(0), res.eltInternal(1)); } @Override public int hashCode() { return (int) (dt.getMillis() ^ dt.getMillis() >>> 32); } @JRubyMethod public RubyFixnum hash(ThreadContext context) { return hashImpl(context.runtime); } private RubyFixnum hashImpl(final Ruby runtime) { return new RubyFixnum(runtime, this.dt.getMillis()); } @Override public RubyFixnum hash() { return hashImpl(getRuntime()); } @JRubyMethod // Get the date as a Julian Day Number. public RubyFixnum jd(ThreadContext context) { return RubyFixnum.newFixnum(context.runtime, getJulianDayNumber()); } public final long getJulianDayNumber() { return DateTimeUtils.toJulianDayNumber(dt.getMillis() + off * 1000); } @JRubyMethod(name = "julian?") public RubyBoolean julian_p(ThreadContext context) { return RubyBoolean.newBoolean(context, isJulian()); } @JRubyMethod(name = "gregorian?") public RubyBoolean gregorian_p(ThreadContext context) { return RubyBoolean.newBoolean(context, ! isJulian()); } public final boolean isJulian() { // JULIAN.<=>(numeric) => +1 if (start == JULIAN) return true; // GREGORIAN.<=>(numeric) => -1 if (start == GREGORIAN) return false; return getJulianDayNumber() < start; } // Get the date as an Astronomical Julian Day Number. @JRubyMethod public IRubyObject ajd(ThreadContext context) { final Ruby runtime = context.runtime; long num = 210_866_760_000_000l + dt.getMillis(); // + subMillis : if (subMillisDen == 1) { num += subMillisNum; return RubyRational.newInstance(context, RubyFixnum.newFixnum(runtime, num), DAY_MS(context)); } RubyNumeric val = (RubyNumeric) RubyFixnum.newFixnum(runtime, num).op_plus(context, subMillis(runtime)); return RubyRational.newRationalConvert(context, val, DAY_MS(context)); } // Get the date as an Astronomical Modified Julian Day Number. @JRubyMethod public IRubyObject amjd(ThreadContext context) { // ajd - MJD_EPOCH_IN_AJD final RubyRational _MJD_EPOCH_IN_AJD = RubyRational.newRational(context.runtime, -4800001, 2); // 1858-11-17 return _MJD_EPOCH_IN_AJD.op_plus(context, ajd(context)); } // When is the Day of Calendar Reform for this Date object? @JRubyMethod public IRubyObject start(ThreadContext context) { Chronology chrono = dt.getChronology(); if (chrono instanceof GregorianChronology) { return getMetaClass().getConstant("GREGORIAN"); // Date::GREGORIAN (-Date::Infinity) } if (chrono instanceof JulianChronology) { return getMetaClass().getConstant("JULIAN"); // Date::JULIAN (+Date::Infinity) } long cutover = DateTimeUtils.toJulianDayNumber(((GJChronology) chrono).getGregorianCutover().getMillis()); return new RubyFixnum(context.runtime, cutover); } final int adjustJodaYear(int year) { if (year < 0 && isJulian()) { // Joda-time returns -x for year x BC in JulianChronology (so there is no year 0), // while date.rb returns -x+1, following astronomical year numbering (with year 0) return ++year; } return year; } @JRubyMethod(name = "year") public RubyInteger year(ThreadContext context) { return RubyFixnum.newFixnum(context.runtime, adjustJodaYear(dt.getYear())); } @JRubyMethod(name = "yday") public RubyInteger yday(ThreadContext context) { return RubyFixnum.newFixnum(context.runtime, dt.getDayOfYear()); } @JRubyMethod(name = "mon", alias = "month") public RubyInteger mon(ThreadContext context) { return RubyFixnum.newFixnum(context.runtime, dt.getMonthOfYear()); } @JRubyMethod(name = "mday", alias = "day") public RubyInteger mday(ThreadContext context) { return RubyFixnum.newFixnum(context.runtime, dt.getDayOfMonth()); } // Get any fractional day part of the date. @JRubyMethod(name = "day_fraction") public RubyNumeric day_fraction(ThreadContext context) { // Rational(millis, 86_400_000) long ms = dt.getSecondOfDay() * 1000L + dt.getMillisOfSecond(); if (subMillisDen == 1) { return (RubyNumeric) RubyRational.newRationalCanonicalize(context, ms + subMillisNum, DAY_MS); } final Ruby runtime = context.runtime; RubyNumeric sum = RubyRational.newRational(runtime, ms, 1).op_plus(context, subMillis(runtime)); return sum.convertToRational().op_div(context, RubyFixnum.newFixnum(runtime, DAY_MS)); } @JRubyMethod(name = "hour", visibility = Visibility.PRIVATE) public RubyInteger hour(ThreadContext context) { return RubyFixnum.newFixnum(context.runtime, dt.getHourOfDay()); } @JRubyMethod(name = "min", alias = "minute", visibility = Visibility.PRIVATE) public RubyInteger minute(ThreadContext context) { return RubyFixnum.newFixnum(context.runtime, dt.getMinuteOfHour()); } @JRubyMethod(name = "sec", alias = "second", visibility = Visibility.PRIVATE) public RubyInteger second(ThreadContext context) { return RubyFixnum.newFixnum(context.runtime, dt.getSecondOfMinute()); } @JRubyMethod(name = "sec_fraction", alias = "second_fraction", visibility = Visibility.PRIVATE) public RubyNumeric sec_fraction(ThreadContext context) { long ms = dt.getMillisOfSecond(); if (subMillisDen == 1) { return (RubyNumeric) RubyRational.newRationalCanonicalize(context, ms + subMillisNum, 1000); } final Ruby runtime = context.runtime; RubyNumeric sum = RubyRational.newRational(runtime, ms, 1).op_plus(context, subMillis(runtime)); return sum.convertToRational().op_div(context, RubyFixnum.newFixnum(runtime, 1000)); } @JRubyMethod public RubyInteger cwyear(ThreadContext context) { return RubyFixnum.newFixnum(context.runtime, adjustJodaYear(dt.getWeekyear())); } @JRubyMethod public RubyInteger cweek(ThreadContext context) { return RubyFixnum.newFixnum(context.runtime, dt.getWeekOfWeekyear()); } @JRubyMethod public RubyInteger cwday(ThreadContext context) { // Monday is commercial day-of-week 1; Sunday is commercial day-of-week 7. return RubyFixnum.newFixnum(context.runtime, dt.getDayOfWeek()); } @JRubyMethod public RubyInteger wday(ThreadContext context) { // Sunday is day-of-week 0; Saturday is day-of-week 6. return RubyFixnum.newFixnum(context.runtime, dt.getDayOfWeek() % 7); } private static final ByteList UTC_ZONE = new ByteList(new byte[] { '+','0','0',':','0','0' }, false); @JRubyMethod(visibility = Visibility.PRIVATE) public RubyString zone(ThreadContext context) { // MRI: m_zone if (this.off == 0) { return RubyString.newUsAsciiStringShared(context.runtime, UTC_ZONE); } return RubyString.newUsAsciiStringNoCopy(context.runtime, of2str(this.off)); } private static final int HOUR_IN_SECONDS = 60 * 60; private static final int MINUTE_IN_SECONDS = 60; private static ByteList of2str(final int of) { // MRI: decode_offset byte s = (byte) ((of < 0) ? '-' : '+'); int a = (of < 0) ? -of : of; int h = a / HOUR_IN_SECONDS; int m = a % HOUR_IN_SECONDS / MINUTE_IN_SECONDS; // format "%c%02d:%02d", s, h, m String digs; ByteList str = new ByteList(6); str.append(s); digs = Integer.toString(h); if (digs.length() == 1) { str.append('0').append(digs.charAt(0)); } else if (digs.length() == 2) { str.append(digs.charAt(0)).append(digs.charAt(1)); } else { append(str, digs); } str.append(':'); digs = Integer.toString(m); if (digs.length() == 1) { str.append('0').append(digs.charAt(0)); } else if (digs.length() == 2) { str.append(digs.charAt(0)).append(digs.charAt(1)); } else { append(str, digs); } return str; } private static void append(final ByteList out, String val) { for (int i=0; i<val.length(); i++) out.append(val.charAt(i)); } // def zone() strftime('%:z') end // private static final int MJD_EPOCH_IN_CJD = 2400001; //private static final int UNIX_EPOCH_IN_CJD = 2440588; private static final int LD_EPOCH_IN_CJD = 2299160; // Get the date as a Modified Julian Day Number. @JRubyMethod public IRubyObject mjd(ThreadContext context) { return RubyFixnum.newFixnum(context.runtime, getJulianDayNumber() - MJD_EPOCH_IN_CJD); } // Get the date as the number of days since the Day of Calendar // Reform (in Italy and the Catholic countries). @JRubyMethod public IRubyObject ld(ThreadContext context) { return RubyFixnum.newFixnum(context.runtime, getJulianDayNumber() - LD_EPOCH_IN_CJD); } // @JRubyMethod(visibility = Visibility.PRIVATE) public IRubyObject offset(ThreadContext context) { final int offset = dt.getChronology().getZone().getOffset(dt); return RubyRational.newRationalCanonicalize(context, offset, DAY_MS); } @JRubyMethod(optional = 1, visibility = Visibility.PRIVATE) public IRubyObject new_offset(ThreadContext context, IRubyObject[] args) { IRubyObject of = args.length > 0 ? args[0] : RubyFixnum.zero(context.runtime); final int off = val2off(context, of); DateTime dt = this.dt.withChronology(getChronology(context, start, off)); return newInstance(context, dt, off, start); } @JRubyMethod public IRubyObject new_start(ThreadContext context) { return newStart(context, ITALY); } // Create a copy of this Date object using a new Day of Calendar Reform. @JRubyMethod public IRubyObject new_start(ThreadContext context, IRubyObject sg) { return newStart(context, val2sg(context, sg)); } private RubyDate newStart(ThreadContext context, final long start) { DateTime dt = this.dt.withChronology(getChronology(context, start, off)); return newInstance(context, dt, off, start, subMillisNum, subMillisDen); } @JRubyMethod public IRubyObject italy(ThreadContext context) { return newStart(context, ITALY); } @JRubyMethod public IRubyObject england(ThreadContext context) { return newStart(context, ENGLAND); } @JRubyMethod public IRubyObject julian(ThreadContext context) { return newStart(context, JULIAN); } @JRubyMethod public IRubyObject gregorian(ThreadContext context) { return newStart(context, GREGORIAN); } @JRubyMethod(name = "julian_leap?", meta = true) public static IRubyObject julian_leap_p(ThreadContext context, IRubyObject self, IRubyObject year) { final RubyInteger y = year.convertToInteger(); return RubyBoolean.newBoolean(context, isJulianLeap(y.getLongValue())); } @JRubyMethod(name = "gregorian_leap?", alias = "leap?", meta = true) public static IRubyObject gregorian_leap_p(ThreadContext context, IRubyObject self, IRubyObject year) { final RubyInteger y = year.convertToInteger(); return RubyBoolean.newBoolean(context, isGregorianLeap(y.getLongValue())); } // All years divisible by 4 are leap years in the Julian calendar. private static boolean isJulianLeap(final long year) { return year % 4 == 0; } // All years divisible by 4 are leap years in the Gregorian calendar, // except for years divisible by 100 and not by 400. private static boolean isGregorianLeap(final long year) { long uy = (year >= 0) ? year : -year; if ((uy & 3) != 0) return false; long century = uy / 100; if (uy != century * 100) return true; return (century & 3) == 0; } @JRubyMethod(name = "leap?") public IRubyObject leap_p(ThreadContext context) { final long year = dt.getYear(); return RubyBoolean.newBoolean(context, isJulian() ? isJulianLeap(year) : isGregorianLeap(year) ); } // @JRubyMethod(name = "+") public IRubyObject op_plus(ThreadContext context, IRubyObject n) { if (n instanceof RubyFixnum) { int days = n.convertToInteger().getIntValue(); return newInstance(context, dt.plusDays(+days), off, start); } if (n instanceof RubyNumeric) { return op_plus_numeric(context, (RubyNumeric) n); } throw context.runtime.newTypeError("expected numeric"); } RubyDate op_plus_numeric(ThreadContext context, RubyNumeric n) { final Ruby runtime = context.runtime; // ms, sub = (n * 86_400_000).divmod(1) // sub = 0 if sub == 0 # avoid Rational(0, 1) // sub_millis = @sub_millis + sub // if sub_millis >= 1 // sub_millis -= 1 // ms += 1 // end RubyNumeric val = (RubyNumeric) RubyFixnum.newFixnum(runtime, DAY_MS).op_mul(context, n); RubyArray res = (RubyArray) val.divmod(context, RubyFixnum.one(runtime)); long ms = ((RubyInteger) res.eltInternal(0)).getLongValue(); RubyNumeric sub = (RubyNumeric) res.eltInternal(1); RubyNumeric sub_millis = subMillis(runtime); if ( sub.isZero() ) ; // done - noop else if ( sub instanceof RubyFloat ) { sub = roundToPrecision(context, (RubyFloat) sub, SUB_MS_PRECISION); sub_millis = (RubyNumeric) sub_millis.op_plus(context, sub); } else { sub_millis = (RubyNumeric) sub_millis.op_plus(context, sub); } long subNum = sub_millis.numerator(context).convertToInteger().getLongValue(); long subDen = sub_millis.denominator(context).convertToInteger().getLongValue(); if (subNum / subDen >= 1) { // sub_millis >= 1 subNum -= subDen; ms += 1; // sub_millis -= 1 } return newInstance(context, dt.plus(ms), off, start, subNum, subDen).normalizeSubMillis(); } static final int SUB_MS_PRECISION = 1_000_000_000; static RubyNumeric roundToPrecision(ThreadContext context, RubyFloat sub, final long precision) { long s = Math.round(sub.getDoubleValue() * precision); return (RubyNumeric) RubyRational.newRationalCanonicalize(context, s, precision); } @JRubyMethod(name = "-") public IRubyObject op_minus(ThreadContext context, IRubyObject n) { if (n instanceof RubyFixnum) { int days = n.convertToInteger().getIntValue(); return newInstance(context, dt.plusDays(-days), off, start); } if (n instanceof RubyNumeric) { return op_plus_numeric(context, (RubyNumeric) ((RubyNumeric) n).op_uminus(context)); } if (n instanceof RubyDate) { return op_minus_date(context, (RubyDate) n); } throw context.runtime.newTypeError("expected numeric or date"); } private RubyNumeric op_minus_date(ThreadContext context, final RubyDate that) { long diff = this.dt.getMillis() - that.dt.getMillis(); RubyNumeric diffMillis = (RubyNumeric) RubyRational.newRationalCanonicalize(context, diff, DAY_MS); RubyNumeric subDiff = subMillisDiff(context, that); if ( ! subDiff.isZero() ) { // diff += diff_sub; subDiff = subDiff.convertToRational().op_div(context, RubyFixnum.newFixnum(context.runtime, DAY_MS)); // #5493 return (RubyNumeric) diffMillis.op_plus(context, subDiff); } return diffMillis; } private RubyNumeric subMillisDiff(final ThreadContext context, final RubyDate that) { final Ruby runtime = context.runtime; long subNum1 = this.subMillisNum; long subNum2 = that.subMillisNum; if (subNum2 == 0) return RubyRational.newRational(runtime, +subNum1, this.subMillisDen); if (subNum1 == 0) return RubyRational.newRational(runtime, -subNum2, that.subMillisDen); if (this.subMillisDen == 1 && that.subMillisDen == 1) { return (RubyInteger) RubyFixnum.newFixnum(runtime, subNum1).op_minus(context, subNum2); } return this.subMillis(runtime).op_minus(context, that.subMillis(runtime)); } final RubyRational subMillis(final Ruby runtime) { return RubyRational.newRational(runtime, subMillisNum, subMillisDen); } // Return a new Date one day after this one. @JRubyMethod(name = "next", alias = "succ") public IRubyObject next(ThreadContext context) { return next_day(context); } @JRubyMethod public IRubyObject next_day(ThreadContext context) { return newInstance(context, dt.plusDays(+1), off, start); } @JRubyMethod public IRubyObject next_day(ThreadContext context, IRubyObject n) { return newInstance(context, dt.plusDays(+simpleIntDiff(n)), off, start); } @JRubyMethod public IRubyObject prev_day(ThreadContext context) { return newInstance(context, dt.plusDays(-1), off, start); } @JRubyMethod public IRubyObject prev_day(ThreadContext context, IRubyObject n) { return newInstance(context, dt.plusDays(-simpleIntDiff(n)), off, start); } @JRubyMethod public IRubyObject next_month(ThreadContext context) { return newInstance(context, dt.plusMonths(+1), off, start); } @JRubyMethod public IRubyObject next_month(ThreadContext context, IRubyObject n) { return newInstance(context, dt.plusMonths(+simpleIntDiff(n)), off, start); } @JRubyMethod public IRubyObject prev_month(ThreadContext context) { return newInstance(context, dt.plusMonths(-1), off, start); } @JRubyMethod public IRubyObject prev_month(ThreadContext context, IRubyObject n) { return newInstance(context, dt.plusMonths(-simpleIntDiff(n)), off, start); } private static int simpleIntDiff(IRubyObject n) { final int days = n.convertToInteger().getIntValue(); if (n instanceof RubyRational) { if (((RubyRational) n).getDenominator().getLongValue() != 1) { return days + 1; // MRI rulez: 1/2 -> 1 (but 0.5 -> 0) } } return days; } @JRubyMethod(name = ">>") public IRubyObject shift_fw(ThreadContext context, IRubyObject n) { return next_month(context, n); } @JRubyMethod(name = "<<") public IRubyObject shift_bw(ThreadContext context, IRubyObject n) { return prev_month(context, n); } @JRubyMethod public IRubyObject next_year(ThreadContext context) { return newInstance(context, dt.plusYears(+1), off, start); } @JRubyMethod public IRubyObject next_year(ThreadContext context, IRubyObject n) { return prevNextYear(context, n, false); } @JRubyMethod public IRubyObject prev_year(ThreadContext context) { return newInstance(context, dt.plusYears(-1), off, start); } @JRubyMethod public IRubyObject prev_year(ThreadContext context, IRubyObject n) { return prevNextYear(context, n, true); } private RubyDate prevNextYear(ThreadContext context, IRubyObject n, final boolean negate) { long months = timesIntDiff(context, n, 12); if (negate) months = -months; // prev_year final int years = RubyNumeric.checkInt(context.runtime, months / 12); return newInstance(context, this.dt.plusYears(years).plusMonths((int) (months % 12)), off, start); } static long timesIntDiff(final ThreadContext context, IRubyObject n, final int times) { IRubyObject mul = RubyFixnum.newFixnum(context.runtime, times).op_mul(context, n); return ((RubyNumeric) mul).round(context).convertToInteger().getLongValue(); } @JRubyMethod // [ ajd, @of, @sg ] public IRubyObject marshal_dump(ThreadContext context) { final Ruby runtime = context.runtime; return context.runtime.newArrayNoCopy(new IRubyObject[] { ajd(context), RubyRational.newRationalCanonicalize(context, off, DAY_IN_SECONDS), RubyFixnum.newFixnum(runtime, start) }); } @JRubyMethod(meta = true) public static RubyDate _load(ThreadContext context, IRubyObject klass, IRubyObject str) { IRubyObject a = RubyMarshal.load(context, null, new IRubyObject[] { str }, null); RubyDate obj = (RubyDate) ((RubyClass) klass).allocate(); return obj.marshal_load(context, a); } @JRubyMethod public RubyDate marshal_load(ThreadContext context, IRubyObject a) { checkFrozen(); if (!(a instanceof RubyArray)) { throw context.runtime.newTypeError("expected an array"); } final RubyArray ary = (RubyArray) a; IRubyObject ajd, of, sg; switch (ary.size()) { case 2: /* 1.6.x */ ajd = valMinusOneHalf(context, ary.eltInternal(0)); of = RubyFixnum.zero(context.runtime); sg = ary.eltInternal(1); if (!k_numeric_p(sg)) { sg = RubyFloat.newFloat(context.runtime, sg.isTrue() ? GREGORIAN_INFINITY : JULIAN_INFINITY); } break; case 3: /* 1.8.x, 1.9.2 */ // ajd, of, sg = a ajd = ary.eltInternal(0); of = ary.eltInternal(1); sg = ary.eltInternal(2); break; case 6: // _, jd, df, sf, of, sg = a IRubyObject jd = ary.eltInternal(1); IRubyObject df = ary.eltInternal(2); IRubyObject sf = ary.eltInternal(3); of = ary.eltInternal(4); sg = ary.eltInternal(5); of = newRationalConvert(context, of, DAY_IN_SECONDS); ajd = marshal_load_6(context, jd, df, sf); break; default: throw context.runtime.newTypeError("invalid size: " + ary.size()); } return initialize(context, ajd, of, sg); } private IRubyObject marshal_load_6(ThreadContext context, IRubyObject jd, IRubyObject df, IRubyObject sf) { IRubyObject ajd = valMinusOneHalf(context, jd); if ( ! ( (RubyNumeric) df ).isZero() ) { ajd = newRationalConvert(context, df, DAY_IN_SECONDS).op_plus(context, ajd); } if ( ! ( (RubyNumeric) sf ).isZero() ) { ajd = newRationalConvert(context, sf, DAY_IN_SECONDS * 1_000_000_000).op_plus(context, ajd); } return ajd; } private static IRubyObject valMinusOneHalf(ThreadContext context, IRubyObject val) { return RubyRational.newRational(context.runtime, -1, 2).op_plus(context, val); } static RubyRational newRationalConvert(ThreadContext context, IRubyObject num, long den) { return (RubyRational) RubyRational.newRationalConvert(context, num, context.runtime.newFixnum(den)); } // def jd_to_ajd(jd, fr, of=0) jd + fr - of - Rational(1, 2) end private static double jd_to_ajd(long jd) { return jd - 0.5; } static RubyNumeric jd_to_ajd(ThreadContext context, long jd) { return RubyRational.newRational(context.runtime, (jd * 2) - 1, 2); } static RubyNumeric jd_to_ajd(ThreadContext context, long jd, RubyNumeric fr, int of_sec) { return jd_to_ajd(context, RubyFixnum.newFixnum(context.runtime, jd), fr, of_sec); } static RubyNumeric jd_to_ajd(ThreadContext context, RubyNumeric jd, RubyNumeric fr, int of_sec) { RubyNumeric tmp = jd; // jd - of : if (of_sec != 0) { tmp = (RubyNumeric) tmp.op_plus(context, RubyRational.newRationalCanonicalize(context, -of_sec, DAY_IN_SECONDS)); } final RubyRational MINUS_HALF = RubyRational.newRational(context.runtime, -1, 2); return (RubyNumeric) ((RubyNumeric) tmp.op_plus(context, fr)).op_plus(context, MINUS_HALF); } @JRubyMethod(meta = true, required = 2, optional = 1, visibility = Visibility.PRIVATE) public static RubyNumeric jd_to_ajd(ThreadContext context, IRubyObject self, IRubyObject[] args) { RubyNumeric jd = (RubyNumeric) args[0]; RubyNumeric fr = (RubyNumeric) args[1]; int of_sec = 0; if (args.length > 2 && ! ((RubyNumeric) args[2]).isZero()) { RubyNumeric of = (RubyNumeric) f_mul(context, args[2], RubyFixnum.newFixnum(context.runtime, DAY_IN_SECONDS)); of_sec = of.getIntValue(); } return jd_to_ajd(context, jd, fr, of_sec); } public static Chronology getChronology(ThreadContext context, final long sg, final int off) { final DateTimeZone zone; if (off == 0) { if (sg == ITALY) return CHRONO_ITALY_UTC; zone = DateTimeZone.UTC; } else { try { zone = DateTimeZone.forOffsetMillis(off * 1000); // off in seconds } // NOTE: JODA only allows 'valid': -23:59:59.999 to +23:59:59.999 catch (IllegalArgumentException ex) { // while MRI handles 25/24 fine debug(context, "invalid offset", ex); throw context.runtime.newArgumentError("invalid offset: " + off); } } return getChronology(context, sg, zone); } static Chronology getChronology(ThreadContext context, final long sg, final DateTimeZone zone) { if (sg == ITALY) return GJChronology.getInstance(zone); if (sg == JULIAN) return JulianChronology.getInstance(zone); if (sg == GREGORIAN) return GregorianChronology.getInstance(zone); Instant cutover = new Instant(DateTimeUtils.fromJulianDay(jd_to_ajd(sg))); try { return GJChronology.getInstance(zone, cutover); } // java.lang.IllegalArgumentException: Cutover too early. Must be on or after 0001-01-01. catch (IllegalArgumentException ex) { debug(context, "invalid date", ex); throw context.runtime.newArgumentError("invalid date"); } } // MRI: #define val2sg(vsg,dsg) static long val2sg(ThreadContext context, IRubyObject sg) { return getValidStart(context, sg.convertToFloat().getDoubleValue(), ITALY); } static long valid_sg(ThreadContext context, IRubyObject sg) { return getValidStart(context, sg.convertToFloat().getDoubleValue(), 0); } // MRI: #define valid_sg(sg) static long getValidStart(final ThreadContext context, final double sg, final int DEFAULT_SG) { // MRI: c_valid_start_p(double sg) if (sg == Double.NEGATIVE_INFINITY || sg == Double.POSITIVE_INFINITY) return (long) sg; if (Double.isNaN(sg) || sg < REFORM_BEGIN_JD && sg > REFORM_END_JD) { RubyKernel.warn(context, null, RubyString.newString(context.runtime, "invalid start is ignored")); return DEFAULT_SG; } ; return (long) sg; } private static final int REFORM_BEGIN_JD = 2298874; /* ns 1582-01-01 */ private static final int REFORM_END_JD = 2426355; /* os 1930-12-31 */ // MRI: #define val2off(vof,iof) static int val2off(ThreadContext context, IRubyObject of) { final int off = offset_to_sec(context, of); if (off == INVALID_OFFSET) { RubyKernel.warn(context, null, RubyString.newString(context.runtime, "invalid offset is ignored")); return 0; } return off; } static void debug(ThreadContext context, final String msg, Exception ex) { if (LOG.isDebugEnabled()) LOG.debug(msg, ex); else if (context.runtime.isDebug()) LOG.info(msg, ex); } @Override public final IRubyObject inspect() { return inspect(getRuntime().getCurrentContext()); } @JRubyMethod public RubyString inspect(ThreadContext context) { int off = this.off; int s = (dt.getHourOfDay() * 60 + dt.getMinuteOfHour()) * 60 + dt.getSecondOfMinute() - off; long ns = (dt.getMillisOfSecond() * 1_000_000) + (subMillisNum * 1_000_000) / subMillisDen; ByteList str = new ByteList(54); // e.g. #<Date: 2018-01-15 ((2458134j,0s,0n),+0s,2299161j)> str.append('#').append('<'); str.append(((RubyString) getMetaClass().to_s()).getByteList()); str.append(':').append(' '); str.append(to_s(context).getByteList()); // to_s str.append(' ').append('(').append('('); str.append(ConvertBytes.longToByteList(getJulianDayNumber(), 10)); str.append('j').append(','); str.append(ConvertBytes.longToByteList(s, 10)); str.append('s').append(','); str.append(ConvertBytes.longToByteList(ns, 10)); str.append('n').append(')'); str.append(','); if (off >= 0) str.append('+'); str.append(ConvertBytes.longToByteList(off, 10)); str.append('s').append(','); if (start == GREGORIAN) { str.append('-').append('I').append('n').append('f'); } else if (start == JULIAN) { str.append('I').append('n').append('f'); } else { str.append(ConvertBytes.longToByteList(start, 10)); } str.append('j').append(')').append('>'); return RubyString.newUsAsciiStringNoCopy(context.runtime, str); } private static final ByteList TO_S_FORMAT = new ByteList(ByteList.plain("%.4d-%02d-%02d"), false); static { TO_S_FORMAT.setEncoding(USASCIIEncoding.INSTANCE); } @Override public final IRubyObject to_s() { return to_s(getRuntime().getCurrentContext()); } @JRubyMethod public RubyString to_s(ThreadContext context) { // format('%.4d-%02d-%02d', year, mon, mday) return format(context, TO_S_FORMAT, year(context), mon(context), mday(context)); } static RubyString format(ThreadContext context, ByteList fmt, IRubyObject... args) { final RubyString str = RubyString.newStringLight(context.runtime, fmt); return str.op_format(context, RubyArray.newArrayNoCopy(context.runtime, args)); } @JRubyMethod public RubyDate to_date() { return this; } @JRubyMethod public RubyDateTime to_datetime(ThreadContext context) { return new RubyDateTime(context.runtime, getDateTime(context.runtime), dt.withTimeAtStartOfDay(), off, start); } @JRubyMethod // Time.local(year, mon, mday) public RubyTime to_time(ThreadContext context) { final Ruby runtime = context.runtime; DateTime dt = this.dt; dt = new DateTime(adjustJodaYear(dt.getYear()), dt.getMonthOfYear(), dt.getDayOfMonth(), 0, 0, 0, RubyTime.getLocalTimeZone(runtime) ); return new RubyTime(runtime, runtime.getTime(), dt); } // date/format.rb @JRubyMethod // def strftime(fmt='%F') public RubyString strftime(ThreadContext context) { return strftime(context, RubyString.newStringLight(context.runtime, DEFAULT_FORMAT_BYTES)); } @JRubyMethod // alias_method :format, :strftime public RubyString strftime(ThreadContext context, IRubyObject fmt) { RubyRational subMillis = this.subMillisNum == 0 ? null : RubyRational.newRational(context.runtime, this.subMillisNum, this.subMillisDen); RubyString format = context.getRubyDateFormatter().compileAndFormat( fmt.convertToString(), true, this.dt, 0, subMillis ); if (fmt.isTaint()) format.setTaint(true); return format; } private static final String DEFAULT_FORMAT = "%F"; private static final ByteList DEFAULT_FORMAT_BYTES = ByteList.create(DEFAULT_FORMAT); static { DEFAULT_FORMAT_BYTES.setEncoding(USASCIIEncoding.INSTANCE); } @JRubyMethod(meta = true) public static IRubyObject _strptime(ThreadContext context, IRubyObject self, IRubyObject string) { return parse(context, string, DEFAULT_FORMAT); } @JRubyMethod(meta = true) public static IRubyObject _strptime(ThreadContext context, IRubyObject self, IRubyObject string, IRubyObject format) { string = string.convertToString(); format = format.convertToString(); return parse(context, string, ((RubyString) format).decodeString()); } static IRubyObject parse(ThreadContext context, IRubyObject string, String format) { string = TypeConverter.checkStringType(context.runtime, string); return new RubyDateParser().parse(context, format, (RubyString) string); } // @Deprecated public static IRubyObject _strptime(ThreadContext context, IRubyObject self, IRubyObject[] args) { switch (args.length) { case 1: return _strptime(context, self, args[0]); case 2: return _strptime(context, self, args[0], args[1]); default: throw context.runtime.newArgumentError(args.length, 1); } } @JRubyMethod(name = "zone_to_diff", meta = true, visibility = Visibility.PRIVATE) public static IRubyObject zone_to_diff(ThreadContext context, IRubyObject self, IRubyObject zone) { final int offset = TimeZoneConverter.dateZoneToDiff(zone.asJavaString()); if (offset == TimeZoneConverter.INVALID_ZONE) return context.nil; return RubyFixnum.newFixnum(context.runtime, offset); } @JRubyMethod(name = "i", meta = true, visibility = Visibility.PRIVATE) public static RubyInteger _i(ThreadContext context, IRubyObject self, IRubyObject val) { return (RubyInteger) TypeConverter.convertToInteger(context, val, 10); // Integer(str, 10) } @JRubyMethod(name = "comp_year69", meta = true, visibility = Visibility.PRIVATE) public static RubyInteger _comp_year69(ThreadContext context, IRubyObject self, IRubyObject year) { RubyInteger y = _i(context, self, year); if (((RubyString) year).strLength() < 4) { final long yi = y.getLongValue(); return RubyFixnum.newFixnum(context.runtime, yi >= 69 ? yi + 1900 : yi + 2000); } return y; } private static final ByteList[] ABBR_DAYS = new ByteList[] { new ByteList(new byte[] { 's','u','n' }, USASCIIEncoding.INSTANCE), new ByteList(new byte[] { 'm','o','n' }, USASCIIEncoding.INSTANCE), new ByteList(new byte[] { 't','u','e' }, USASCIIEncoding.INSTANCE), new ByteList(new byte[] { 'w','e','d' }, USASCIIEncoding.INSTANCE), new ByteList(new byte[] { 't','h','u' }, USASCIIEncoding.INSTANCE), new ByteList(new byte[] { 'f','r','i' }, USASCIIEncoding.INSTANCE), new ByteList(new byte[] { 's','a','t' }, USASCIIEncoding.INSTANCE), }; private static int day_num(RubyString s) { ByteList sb = s.getByteList(); int i; for (i=0; i<ABBR_DAYS.length; i++) { if (sb.caseInsensitiveCmp(ABBR_DAYS[i]) == 0) return i; } return -1; } private static final ByteList _parse_time, _parse_time2; static { _parse_time = ByteList.create( "(" + "(?:" + "\\d+\\s*:\\s*\\d+" + "(?:" + "\\s*:\\s*\\d+(?:[,.]\\d+)?" + ")?" + "|" + "\\d+\\s*h(?:\\s*\\d+m?(?:\\s*\\d+s?)?)?" + ")" + "(?:" + "\\s*" + "[ap](?:m\\b|\\.m\\.)" + ")?" + "|" + "\\d+\\s*[ap](?:m\\b|\\.m\\.)" + ")" + "(?:" + "\\s*" + "(" + "(?:gmt|utc?)?[-+]\\d+(?:[,.:]\\d+(?::\\d+)?)?" + "|" + "(?-i:[[:alpha:].\\s]+)(?:standard|daylight)\\stime\\b" + "|" + "(?-i:[[:alpha:]]+)(?:\\sdst)?\\b" + ")" + ")?" ); _parse_time.setEncoding(USASCIIEncoding.INSTANCE); _parse_time2 = ByteList.create( "\\A(\\d+)h?" + "(?:\\s*:?\\s*(\\d+)m?" + "(?:" + "\\s*:?\\s*(\\d+)(?:[,.](\\d+))?s?" + ")?" + ")?" + "(?:\\s*([ap])(?:m\\b|\\.m\\.))?" ); _parse_time2.setEncoding(USASCIIEncoding.INSTANCE); } static IRubyObject _parse_time(ThreadContext context, IRubyObject self, RubyString str, RubyHash hash) { final Ruby runtime = context.runtime; RubyRegexp re = newRegexpFromCache(runtime, _parse_time, RE_OPTION_IGNORECASE | RE_OPTION_EXTENDED); IRubyObject sub = subSpace(context, str, re); if (sub != context.nil) { RubyMatchData match = (RubyMatchData) sub; final RubyString s1 = (RubyString) match.at(1); final RubyString s2 = matchOrNull(context, match, 2); if (s2 != null) hash.fastASet(runtime.newSymbol("zone"), s2); re = newRegexpFromCache(runtime, _parse_time2, RE_OPTION_IGNORECASE | RE_OPTION_EXTENDED); sub = re.match_m(context, s1, false); if (sub != context.nil) { match = (RubyMatchData) sub; RubyInteger hour; RubyString m = (RubyString) match.at(1); hash.fastASet(runtime.newSymbol("hour"), hour = (RubyInteger) m.to_i()); m = matchOrNull(context, match, 2); if (m != null) hash.fastASet(runtime.newSymbol("min"), m.to_i()); m = matchOrNull(context, match, 3); if (m != null) hash.fastASet(runtime.newSymbol("sec"), m.to_i()); m = matchOrNull(context, match, 4); if (m != null) { RubyInteger den = (RubyInteger) RubyFixnum.newFixnum(runtime, 10).op_pow(context, m.length()); hash.fastASet(runtime.newSymbol("sec_fraction"), RubyRational.newInstance(context, (RubyInteger) m.to_i(), den)); } m = matchOrNull(context, match, 5); if (m != null) { hour = (RubyInteger) hour.op_mod(context, 12); if (m.length() == 1 && strPtr(m, 'p') || strPtr(m, 'P')) { hour = (RubyInteger) hour.op_plus(context, 12); } hash.fastASet(runtime.newSymbol("hour"), hour); } } else { hash.fastASet(runtime.newSymbol("hour"), RubyFixnum.zero(runtime)); } return context.tru; } return sub; // nil } private static final ByteList[] ABBR_MONTHS = new ByteList[] { new ByteList(new byte[] { 'j','a','n' }, USASCIIEncoding.INSTANCE), new ByteList(new byte[] { 'f','e','b' }, USASCIIEncoding.INSTANCE), new ByteList(new byte[] { 'm','a','r' }, USASCIIEncoding.INSTANCE), new ByteList(new byte[] { 'a','p','r' }, USASCIIEncoding.INSTANCE), new ByteList(new byte[] { 'm','a','y' }, USASCIIEncoding.INSTANCE), new ByteList(new byte[] { 'j','u','n' }, USASCIIEncoding.INSTANCE), new ByteList(new byte[] { 'j','u','l' }, USASCIIEncoding.INSTANCE), new ByteList(new byte[] { 'a','u','g' }, USASCIIEncoding.INSTANCE), new ByteList(new byte[] { 's','e','p' }, USASCIIEncoding.INSTANCE), new ByteList(new byte[] { 'o','c','t' }, USASCIIEncoding.INSTANCE), new ByteList(new byte[] { 'n','o','v' }, USASCIIEncoding.INSTANCE), new ByteList(new byte[] { 'd','e','c' }, USASCIIEncoding.INSTANCE), }; private static int mon_num(RubyString s) { ByteList sb = s.getByteList(); int i; for (i=0; i<ABBR_MONTHS.length; i++) { if (sb.caseInsensitiveCmp(ABBR_MONTHS[i]) == 0) return i + 1; } return -1; } private static final ByteList _parse_day; static { _parse_day = ByteList.create("\\b(sun|mon|tue|wed|thu|fri|sat)[^-\\d\\s]*"); _parse_day.setEncoding(USASCIIEncoding.INSTANCE); } static IRubyObject _parse_day(ThreadContext context, IRubyObject self, RubyString str, RubyHash hash) { final Ruby runtime = context.runtime; RubyRegexp re = newRegexpFromCache(runtime, _parse_day, RE_OPTION_IGNORECASE); IRubyObject sub = subSpace(context, (RubyString) str, re); if (sub != context.nil) { int day = day_num((RubyString) ((RubyMatchData) sub).at(1)); hash.fastASet(runtime.newSymbol("wday"), RubyFixnum.newFixnum(runtime, day)); return context.tru; } return sub; // nil } private static final ByteList _parse_mon; static { _parse_mon = ByteList.create("\\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\\S*"); _parse_mon.setEncoding(USASCIIEncoding.INSTANCE); } static IRubyObject _parse_mon(ThreadContext context, IRubyObject self, RubyString str, RubyHash hash) { final Ruby runtime = context.runtime; RubyRegexp re = newRegexpFromCache(runtime, _parse_mon, RE_OPTION_IGNORECASE); IRubyObject sub = subSpace(context, (RubyString) str, re); if (sub != context.nil) { int mon = mon_num((RubyString) ((RubyMatchData) sub).at(1)); hash.fastASet(runtime.newSymbol("mon"), RubyFixnum.newFixnum(runtime, mon)); return context.tru; } return sub; // nil } private static final ByteList _parse_year; static { _parse_year = ByteList.create("'(\\d+)\\b"); _parse_year.setEncoding(USASCIIEncoding.INSTANCE); } static IRubyObject _parse_year(ThreadContext context, IRubyObject self, RubyString str, RubyHash hash) { final Ruby runtime = context.runtime; RubyRegexp re = RubyRegexp.newRegexp(runtime, _parse_year); IRubyObject sub = subSpace(context, (RubyString) str, re); if (sub != context.nil) { hash.fastASet(runtime.newSymbol("year"), ((RubyString) ((RubyMatchData) sub).at(1)).to_i()); return context.tru; } return sub; // nil } private static final ByteList _parse_mday; static { _parse_mday = ByteList.create("(\\d+)(st|nd|rd|th)\\b"); _parse_mday.setEncoding(USASCIIEncoding.INSTANCE); } static IRubyObject _parse_mday(ThreadContext context, IRubyObject self, RubyString str, RubyHash hash) { final Ruby runtime = context.runtime; RubyRegexp re = newRegexpFromCache(runtime, _parse_mday, RE_OPTION_IGNORECASE); IRubyObject sub = subSpace(context, (RubyString) str, re); if (sub != context.nil) { hash.fastASet(runtime.newSymbol("mday"), ((RubyString) ((RubyMatchData) sub).at(1)).to_i()); return context.tru; } return sub; // nil } private static final ByteList _parse_eu; static { _parse_eu = ByteList.create( "('?\\d+)[^-\\d\\s]*" + "\\s*" + "(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[^-\\d\\s']*" + "(?:" + "\\s*" + "(c(?:e|\\.e\\.)|b(?:ce|\\.c\\.e\\.)|a(?:d|\\.d\\.)|b(?:c|\\.c\\.))?" + "\\s*" + "('?-?\\d+(?:(?:st|nd|rd|th)\\b)?)" + ")?" ); _parse_eu.setEncoding(USASCIIEncoding.INSTANCE); } static IRubyObject _parse_eu(ThreadContext context, IRubyObject self, RubyString str, RubyHash hash) { final Ruby runtime = context.runtime; RubyRegexp re = newRegexpFromCache(runtime, _parse_eu, RE_OPTION_IGNORECASE); IRubyObject sub = subSpace(context, (RubyString) str, re); if (sub != context.nil) { final RubyMatchData match = (RubyMatchData) sub; RubyString d = (RubyString) match.at(1); RubyString mon = (RubyString) match.at(2); mon = RubyString.newString(runtime, ConvertBytes.longToByteList(mon_num(mon))); RubyString b = matchOrNull(context, match, 3); RubyString y = matchOrNull(context, match, 4); s3e(context, hash, y, mon, d, b != null && b.length() > 1 && (b.charAt(0) == 'B' || b.charAt(0) == 'b')); return context.tru; } return sub; // nil } private static final ByteList _parse_us; static { _parse_us = ByteList.create( "\\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[^-\\d\\s']*" + "\\s*" + "('?\\d+)[^-\\d\\s']*" + "(?:" + "\\s*,?" + "\\s*" + "(c(?:e|\\.e\\.)|b(?:ce|\\.c\\.e\\.)|a(?:d|\\.d\\.)|b(?:c|\\.c\\.))?" + "\\s*" + "('?-?\\d+)" + ")?" ); _parse_us.setEncoding(USASCIIEncoding.INSTANCE); } static IRubyObject _parse_us(ThreadContext context, IRubyObject self, RubyString str, RubyHash hash) { final Ruby runtime = context.runtime; RubyRegexp re = newRegexpFromCache(runtime, _parse_us, RE_OPTION_IGNORECASE); IRubyObject sub = subSpace(context, (RubyString) str, re); if (sub != context.nil) { final RubyMatchData match = (RubyMatchData) sub; RubyString mon = (RubyString) match.at(1); mon = RubyString.newString(runtime, ConvertBytes.longToByteList(mon_num(mon))); RubyString d = (RubyString) match.at(2); RubyString b = matchOrNull(context, match, 3); RubyString y = matchOrNull(context, match, 4); s3e(context, hash, y, mon, d, b != null && b.length() > 1 && (b.charAt(0) == 'B' || b.charAt(0) == 'b')); return context.tru; } return sub; // nil } // NOTE: without this things get slower than the .rb version of _parse_eu/_parse_us etc. private static RubyRegexp newRegexpFromCache(Ruby runtime, ByteList str, int opts) { RegexpOptions options = RegexpOptions.fromEmbeddedOptions(opts); Regex pattern = getRegexpFromCache(runtime, str, str.getEncoding(), options); return new RubyRegexp(runtime, pattern, str, options); } private static RubyString matchOrNull(ThreadContext context, final RubyMatchData match, int i) { IRubyObject val = match.at(i); return val == context.nil ? null : (RubyString) val; } private static final ByteList _parse_iso; static { _parse_iso = ByteList.create("('?[-+]?\\d+)-(\\d+)-('?-?\\d+)"); _parse_iso.setEncoding(USASCIIEncoding.INSTANCE); } static IRubyObject _parse_iso(ThreadContext context, IRubyObject self, RubyString str, RubyHash hash) { final Ruby runtime = context.runtime; RubyRegexp re = RubyRegexp.newRegexp(runtime, _parse_iso); IRubyObject sub = subSpace(context, (RubyString) str, re); if (sub != context.nil) { final RubyMatchData match = (RubyMatchData) sub; s3e(context, hash, (RubyString) match.at(1), (RubyString) match.at(2), (RubyString) match.at(3), false); return context.tru; } return sub; // nil } private static final ByteList _parse_sla; static { _parse_sla = ByteList.create("('?-?\\d+)/\\s*('?\\d+)(?:\\D\\s*('?-?\\d+))?"); _parse_sla.setEncoding(USASCIIEncoding.INSTANCE); } static IRubyObject _parse_sla(ThreadContext context, IRubyObject self, RubyString str, RubyHash hash) { return parse_sla_dot(context, _parse_sla, str, hash); } private static final ByteList _parse_dot; static { _parse_dot = ByteList.create("('?-?\\d+)\\.\\s*('?\\d+)\\.\\s*('?-?\\d+)"); _parse_dot.setEncoding(USASCIIEncoding.INSTANCE); } static IRubyObject _parse_dot(ThreadContext context, IRubyObject self, RubyString str, RubyHash hash) { return parse_sla_dot(context, _parse_dot, str, hash); } private static IRubyObject parse_sla_dot(ThreadContext context, ByteList pattern, RubyString str, RubyHash hash) { final Ruby runtime = context.runtime; RubyRegexp re = RubyRegexp.newRegexp(runtime, pattern); IRubyObject sub = subSpace(context, str, re); if (sub != context.nil) { final RubyMatchData match = (RubyMatchData) sub; RubyString y = matchOrNull(context, match, 1); RubyString mon = matchOrNull(context, match, 2); RubyString d = matchOrNull(context, match, 3); s3e(context, hash, y, mon, d, false); return context.tru; } return sub; // nil } private static final ByteList _parse_bc; static { _parse_bc = ByteList.create("\\b(bc\\b|bce\\b|b\\.c\\.|b\\.c\\.e\\.)"); _parse_bc.setEncoding(USASCIIEncoding.INSTANCE); } static void parse_bc(ThreadContext context, IRubyObject self, RubyString str, RubyHash hash) { final Ruby runtime = context.runtime; RubyRegexp re = newRegexpFromCache(runtime, _parse_bc, RE_OPTION_IGNORECASE); IRubyObject sub = subSpace(context, (RubyString) str, re); if (sub != context.nil) { //set_hash(context, (RubyHash) h, "_bc", context.tru); } boolean bc = sub != context.nil; if (bc || hashGetTest(context, hash, "_bc")) { // if (RTEST(ref_hash("_bc"))) part from _parse RubyInteger y; y = (RubyInteger) hashGet(context, hash, "year"); if (y != null) { set_hash(context, hash, "year", y.negate().op_plus(context, 1)); } y = (RubyInteger) hashGet(context, hash, "cwyear"); if (y != null) { set_hash(context, hash, "cwyear", y.negate().op_plus(context, 1)); } } } private static final ByteList _parse_frag; static { _parse_frag = ByteList.create("\\A\\s*(\\d{1,2})\\s*\\z"); _parse_frag.setEncoding(USASCIIEncoding.INSTANCE); } static void parse_frag(ThreadContext context, IRubyObject self, RubyString str, RubyHash hash) { final Ruby runtime = context.runtime; IRubyObject sub = null; if (hashGet(context, hash, "hour") != null && hashGet(context, hash, "mday") == null) { RubyRegexp re = newRegexpFromCache(runtime, _parse_frag, RE_OPTION_IGNORECASE); sub = subSpace(context, (RubyString) str, re); if (sub != context.nil) { RubyInteger v = (RubyInteger) ((RubyString) ((RubyMatchData) sub).at(1)).to_i(); long vi = v.getLongValue(); if (1 <= vi && vi <= 31) hash.fastASet(runtime.newSymbol("mday"), v); } } if (hashGet(context, hash, "mday") != null && hashGet(context, hash, "hour") == null) { if (sub == null) { RubyRegexp re = newRegexpFromCache(runtime, _parse_frag, RE_OPTION_IGNORECASE); sub = subSpace(context, (RubyString) str, re); } if (sub != context.nil) { RubyInteger v = (RubyInteger) ((RubyString) ((RubyMatchData) sub).at(1)).to_i(); long vi = v.getLongValue(); if (0 <= vi && vi <= 24) hash.fastASet(runtime.newSymbol("hour"), v); } } } private static IRubyObject hashGet(final ThreadContext context, final RubyHash hash, final String key) { IRubyObject val = hash.fastARef(context.runtime.newSymbol(key)); if (val == null || val == context.nil) return null; return val; } private static boolean hashGetTest(final ThreadContext context, final RubyHash hash, final String key) { IRubyObject val = hash.fastARef(context.runtime.newSymbol(key)); if (val == null || val == context.nil) return false; return val.isTrue(); } private static final ByteList SPACE = new ByteList(new byte[] { ' ' }, false); private static IRubyObject subSpace(ThreadContext context, RubyString str, RubyRegexp reg) { return str.subBangFast(context, reg, RubyString.newStringShared(context.runtime, SPACE)); } // NOTE: still in .rb public static IRubyObject _parse_jis(ThreadContext context, IRubyObject self, IRubyObject str, IRubyObject h) { return Helpers.invoke(context, self, "_parse_jis", str, h); } // NOTE: still in .rb public static IRubyObject _parse_vms(ThreadContext context, IRubyObject self, IRubyObject str, IRubyObject h) { return Helpers.invoke(context, self, "_parse_vms", str, h); } // NOTE: still in .rb public static IRubyObject _parse_iso2(ThreadContext context, IRubyObject self, IRubyObject str, IRubyObject h) { return Helpers.invoke(context, self, "_parse_iso2", str, h); } // NOTE: still in .rb public static IRubyObject _parse_ddd(ThreadContext context, IRubyObject self, IRubyObject str, IRubyObject h) { return Helpers.invoke(context, self, "_parse_ddd", str, h); } private static final ByteList _parse_impl; static { _parse_impl = ByteList.create("[^-+',.\\/:@[:alnum:]\\[\\]]+"); _parse_impl.setEncoding(USASCIIEncoding.INSTANCE); } @JRubyMethod(name = "_parse_impl", meta = true, visibility = Visibility.PRIVATE) public static IRubyObject _parse_impl(ThreadContext context, IRubyObject self, IRubyObject s, IRubyObject h) { final Ruby runtime = context.runtime; RubyString str = (RubyString) s; RubyHash hash = (RubyHash) h; str = str.gsubFast(context, newRegexp(runtime, _parse_impl), RubyString.newStringShared(context.runtime, SPACE), Block.NULL_BLOCK); int flags = check_class(str); if ((flags & HAVE_ALPHA) == HAVE_ALPHA) { _parse_day(context, self, str, hash); } if ((flags & HAVE_DIGIT) == HAVE_DIGIT && ((flags & (HAVE_COLON|HAVE_M_m|HAVE_H_h|HAVE_S_s)) != 0)) { // JRuby opt _parse_time(context, self, str, hash); } do_parse(context, self, str, hash, flags); // ok: if ((flags & HAVE_B_b) == HAVE_B_b) { // JRuby opt - instead of HAVE_ALPHA parse_bc(context, self, str, hash); } if ((flags & HAVE_DIGIT) == HAVE_DIGIT) { // NOTE: MRI re-loops string parse_frag(context, self, str, hash); } if (hashGetTest(context, hash, "_comp")) { RubyInteger y; y = (RubyInteger) hashGet(context, hash, "cwyear"); if (y != null) { long yi = y.getLongValue(); if (yi >= 0 && yi <= 99) { set_hash(context, hash, "cwyear", y.op_plus(context, yi >= 69 ? 1900 : 2000)); } } y = (RubyInteger) hashGet(context, hash, "year"); if (y != null) { long yi = y.getLongValue(); if (yi >= 0 && yi <= 99) { set_hash(context, hash, "year", y.op_plus(context, yi >= 69 ? 1900 : 2000)); } } } IRubyObject zone; if (hashGet(context, hash, "offset") == null && (zone = hashGet(context, hash, "zone")) != null) { set_hash(context, hash, "offset", zone_to_diff(context, self, zone)); } hash.fastDelete(runtime.newSymbol("_bc")); hash.fastDelete(runtime.newSymbol("_comp")); return hash; } private static void do_parse(ThreadContext context, IRubyObject self, RubyString str, RubyHash hash, final int flags) { //#ifdef TIGHT_PARSER // if (HAVE_ELEM_P(HAVE_ALPHA)) parse_era(str, hash); //#endif IRubyObject res; if ((flags & (HAVE_ALPHA|HAVE_DIGIT)) == (HAVE_ALPHA|HAVE_DIGIT)) { res = _parse_eu(context, self, str, hash); if (res != context.nil) return; res = _parse_us(context, self, str, hash); if (res != context.nil) return; } if ((flags & (HAVE_DIGIT|HAVE_DASH)) == (HAVE_DIGIT|HAVE_DASH)) { res = _parse_iso(context, self, str, hash); if (res != context.nil) return; } if ((flags & (HAVE_DIGIT|HAVE_DOT)) == (HAVE_DIGIT|HAVE_DOT)) { res = _parse_jis(context, self, str, hash); if (res != context.nil) return; } if ((flags & (HAVE_ALPHA|HAVE_DIGIT|HAVE_DASH)) == (HAVE_ALPHA|HAVE_DIGIT|HAVE_DASH)) { res = _parse_vms(context, self, str, hash); if (res != context.nil) return; } if ((flags & (HAVE_DIGIT|HAVE_SLASH)) == (HAVE_DIGIT|HAVE_SLASH)) { res = _parse_sla(context, self, str, hash); if (res != context.nil) return; } if ((flags & (HAVE_DIGIT|HAVE_DOT)) == (HAVE_DIGIT|HAVE_DOT)) { res = _parse_dot(context, self, str, hash); if (res != context.nil) return; } if ((flags & HAVE_DIGIT) == HAVE_DIGIT) { res = _parse_iso2(context, self, str, hash); if (res != context.nil) return; } if ((flags & HAVE_DIGIT) == HAVE_DIGIT) { res = _parse_year(context, self, str, hash); if (res != context.nil) return; } if ((flags & HAVE_ALPHA) == HAVE_ALPHA) { res = _parse_mon(context, self, str, hash); if (res != context.nil) return; } if ((flags & HAVE_DIGIT) == HAVE_DIGIT) { res = _parse_mday(context, self, str, hash); if (res != context.nil) return; } if ((flags & HAVE_DIGIT) == HAVE_DIGIT) { res = _parse_ddd(context, self, str, hash); if (res != context.nil) return; } // MRI does an ERROR here ... } private static final int HAVE_ALPHA = (1<<0); private static final int HAVE_DIGIT = (1<<1); private static final int HAVE_DASH = (1<<2); private static final int HAVE_DOT = (1<<3); private static final int HAVE_SLASH = (1<<4); // custom, not in MRI : private static final int HAVE_COLON = (1<<6); private static final int HAVE_M_m = (1<<7); // am|pm 3m private static final int HAVE_H_h = (1<<8); // 9h private static final int HAVE_S_s = (1<<9); // 3s private static final int HAVE_B_b = (1<<10); // bc private static int check_class(RubyString s) { // TODO: we could assume single-byte like MRI, right? int flags = 0; for (int i=0; i<s.length(); i++) { final char c = s.charAt(i); switch (c) { case '-': flags |= HAVE_DASH; break; case '.': flags |= HAVE_DOT; break; case '/': flags |= HAVE_SLASH; break; case ':': flags |= HAVE_COLON; break; case 'b': case 'B': flags |= HAVE_ALPHA|HAVE_B_b; break; case 'm': case 'M': flags |= HAVE_ALPHA|HAVE_M_m; break; case 'h': case 'H': flags |= HAVE_ALPHA|HAVE_H_h; break; case 's': case 'S': flags |= HAVE_ALPHA|HAVE_S_s; break; default: if (isDigit(c)) flags |= HAVE_DIGIT; else if (isAlpha(c)) flags |= HAVE_ALPHA; } } return flags; } // str.sub! /reg/, ' ' (without $~) @JRubyMethod(name = "subs", meta = true, visibility = Visibility.PRIVATE) public static IRubyObject _subs(ThreadContext context, IRubyObject self, IRubyObject str, IRubyObject reg) { return subSpace(context, (RubyString) str, (RubyRegexp) reg); } // /re/.match str (without $~) @JRubyMethod(name = "match", meta = true, visibility = Visibility.PRIVATE) public static IRubyObject _match(ThreadContext context, IRubyObject self, IRubyObject reg, IRubyObject str) { return ((RubyRegexp) reg).match_m(context, str, false); } @JRubyMethod(name = "s3e", meta = true, required = 4, optional = 1, visibility = Visibility.PRIVATE) public static IRubyObject _s3e(ThreadContext context, IRubyObject self, IRubyObject[] args) { final IRubyObject nil = context.nil; RubyString y = args[1] == nil ? null : (RubyString) args[1]; RubyString m = args[2] == nil ? null : (RubyString) args[2]; RubyString d = args[3] == nil ? null : (RubyString) args[3]; return s3e(context, (RubyHash) args[0], y, m, d, args.length > 4 ? args[4].isTrue() : false); } private static IRubyObject s3e(ThreadContext context, final RubyHash hash, RubyString y, RubyString m, RubyString d, boolean bc) { Boolean comp = null; RubyString oy, om, od; if (d == null && y != null && m != null) { oy = y; om = m; od = d; y = od; m = oy; d = om; } if (y == null) { if (d != null && d.strLength() > 2) { y = d; d = null; } else if (d != null && strPtr(d, '\'')) { y = d; d = null; } } if (y != null) { int s = skipNonDigitsAndSign(y); int bp = s; char c = s < y.strLength() ? y.charAt(s) : '\0'; if (c == '+' || c == '-') s++; int ep = skipDigits(y, s); if (ep != y.strLength()) { oy = y; y = d; d = (RubyString) oy.substr19(context.runtime, bp, ep - bp); } } if (m != null) { if (strPtr(m, '\'') || m.strLength() > 2) { /* us -> be */ oy = y; om = m; od = d; y = om; m = od; d = oy; } } if (d != null) { if (strPtr(d, '\'') || d.strLength() > 2) { oy = y; od = d; y = od; d = oy; } } if (y != null) { boolean sign = false; int s = skipNonDigitsAndSign(y); int bp = s; char c = s < y.strLength() ? y.charAt(s) : '\0'; if (c == '+' || c == '-') { s++; sign = true; } if (sign) comp = false; int ep = skipDigits(y, s); if (ep - s > 2) comp = false; RubyInteger iy = cstr2num(context.runtime, y, bp, ep); if (bc) iy = (RubyInteger) iy.negate().op_plus(context, 1); set_hash(context, hash, "year", iy); } //if (bc) set_hash("_bc", Qtrue); if (m != null) { int s = skipNonDigitsAndSign(m); int bp = s; int ep = skipDigits(m, s); set_hash(context, hash, "mon", cstr2num(context.runtime, m, bp, ep)); } if (d != null) { int s = skipNonDigitsAndSign(d); int bp = s; int ep = skipDigits(d, s); set_hash(context, hash, "mday", cstr2num(context.runtime, d, bp, ep)); } if (comp != null) set_hash(context, hash, "_comp", RubyBoolean.newBoolean(context, comp)); return hash; } private static void set_hash(final ThreadContext context, RubyHash hash, String key, IRubyObject val) { hash.fastASet(context.runtime.newSymbol(key), val); } private static RubyInteger cstr2num(Ruby runtime, RubyString str, int bp, int ep) { if (bp == ep) return RubyFixnum.zero(runtime); return ConvertBytes.byteListToInum(runtime, str.getByteList(), bp, ep, 10, true); } private static boolean strPtr(RubyString str, char c) { return str.strLength() > 0 && str.charAt(0) == c; } private static boolean isDigit(char c) { switch (c) { case '0': case '1': case '2': case '3': case '4': return true; case '5': case '6': case '7': case '8': case '9': return true; default: return false; } } private static boolean isAlpha(char c) { return Character.isLetter(c); } private static int skipNonDigitsAndSign(RubyString str) { int s = 0; while (s < str.length()) { char c = str.charAt(s); if (isDigit(c) || (c == '+' || c == '-')) break; s++; } return s; } private static int skipDigits(RubyString str, int off) { int i = off; for (; i < str.length(); i++) { if (!isDigit(str.charAt(i))) return i; } return i; } // Java API
Returns:year
/** * @return year */
public int getYear() { return dt.getYear(); }
Returns:month-of-year (1..12)
/** * @return month-of-year (1..12) */
public int getMonth() { return dt.getMonthOfYear(); }
Returns:day-of-month
/** * @return day-of-month */
public int getDay() { return dt.getDayOfMonth(); }
Returns:hour-of-day (0..23)
/** * @return hour-of-day (0..23) */
public int getHour() { return dt.getHourOfDay(); }
Returns:minute-of-hour
/** * @return minute-of-hour */
public int getMinute() { return dt.getMinuteOfHour(); }
Returns:second-of-minute
/** * @return second-of-minute */
public int getSecond() { return dt.getSecondOfMinute(); }
Returns:the nano second part (only) of time
/** * @return the nano second part (only) of time */
public int getNanos() { final Ruby runtime = getRuntime(); final ThreadContext context = runtime.getCurrentContext(); RubyNumeric usec = (RubyNumeric) subMillis(runtime).op_mul(context, RubyFixnum.newFixnum(runtime, 1_000_000)); return (int) usec.getLongValue(); } public Date toDate() { return this.dt.toDate(); }
Returns:an instant
/** * @return an instant */
public java.time.Instant toInstant() { return java.time.Instant.ofEpochMilli(dt.getMillis()).plusNanos(getNanos()); }
Returns:a (local) date
/** * @return a (local) date */
public LocalDate toLocalDate() { return LocalDate.of(getYear(), getMonth(), getDay()); } @Override public Class getJavaClass() { return Date.class; // for compatibility with RubyTime } @Override public <T> T toJava(Class<T> target) { // retain compatibility with RubyTime (`target.isAssignableFrom(Date.class)`) if (target == Date.class || target == Comparable.class || target == Object.class) { return target.cast(toDate()); } if (target == Calendar.class || target == GregorianCalendar.class) { return target.cast(dt.toGregorianCalendar()); } // target == Comparable.class and target == Object.class already handled above if (target.isAssignableFrom(DateTime.class) && target != Serializable.class) { return target.cast(this.dt); } // SQL if (target == java.sql.Date.class) { return target.cast(new java.sql.Date(dt.getMillis())); } if (target == java.sql.Time.class) { return target.cast(new java.sql.Time(dt.getMillis())); } if (target == java.sql.Timestamp.class) { java.sql.Timestamp timestamp = new java.sql.Timestamp(dt.getMillis()); timestamp.setNanos(getNanos()); return target.cast(timestamp); } // Java 8 if (target != Serializable.class) { if (target.isAssignableFrom(java.time.Instant.class)) { // covers Temporal/TemporalAdjuster return (T) toInstant(); } if (target.isAssignableFrom(LocalDate.class)) { // java.time.chrono.ChronoLocalDate.class return (T) toLocalDate(); } } return super.toJava(target); } }