Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 20d3053ffc89
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 616daacba424
Choose a head ref
  • 8 commits
  • 8 files changed
  • 2 contributors

Commits on Jun 8, 2017

  1. Copy the full SHA
    b70b88b View commit details
  2. Copy the full SHA
    e430eaa View commit details
  3. Add RubyDateParser

    muga committed Jun 8, 2017
    Copy the full SHA
    695610f View commit details
  4. Copy the full SHA
    1b66b58 View commit details
  5. Change data types of cwyear, year, sec_fraction, seconds and _cent in…

    … FormatBag with BigInteger
    muga committed Jun 8, 2017
    Copy the full SHA
    d16ef43 View commit details
  6. Copy the full SHA
    6c71044 View commit details
  7. Add JRuby license notation

    muga committed Jun 8, 2017
    Copy the full SHA
    886deb6 View commit details

Commits on Jun 13, 2017

  1. Merge pull request #4635 from muga/implement_rubydateparser_in_java

    [WIP] Implement RubyDateParser in Java
    headius authored Jun 13, 2017
    Copy the full SHA
    616daac View commit details
62 changes: 62 additions & 0 deletions core/src/main/java/org/jruby/lexer/StrptimeLexer.flex
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* IMPORTANT: must compile with JFlex 1.4, JFlex 1.4.3 seems buggy with look-ahead */

package org.jruby.lexer;

import org.jruby.util.StrptimeToken;

%%
%public
%class StrptimeLexer
//%debug
%unicode
%type org.jruby.util.StrptimeToken
%{
StringBuilder stringBuf = new StringBuilder();

public StrptimeToken rawString() {
String str = stringBuf.toString();
stringBuf.setLength(0);
return StrptimeToken.str(str);
}

public StrptimeToken directive(char c) {
StrptimeToken token;
if (c == 'z') {
int colons = yylength()-1; // can only be colons except the 'z'
return StrptimeToken.zoneOffsetColons(colons);
} else if ((token = StrptimeToken.format(c)) != null) {
return token;
} else {
return StrptimeToken.special(c);
}
}
%}

Flags = [-_0#\^]+
Width = [1-9][0-9]*

// See RubyDateFormatter.main to generate this
// Chars are sorted by | ruby -e 'p STDIN.each_char.sort{|a,b|a.casecmp(b).tap{|c|break a<=>b if c==0}}.join'
Conversion = [\+AaBbCcDdeFGgHhIjkLlMmNnPpQRrSsTtUuVvWwXxYyZz] | {IgnoredModifier} | {Zone}
// From MRI strftime.c
IgnoredModifier = E[CcXxYy] | O[deHIMmSUuVWwy]
Zone = :{1,3} z

SimpleDirective = "%"
LiteralPercent = "%%"
Unknown = .|\n

%xstate CONVERSION

%%

<YYINITIAL> {
{LiteralPercent} { return StrptimeToken.str("%"); }
{SimpleDirective} / {Conversion} { yybegin(CONVERSION); }
}

<CONVERSION> {Conversion} { yybegin(YYINITIAL); return directive(yycharat(yylength()-1)); }

/* fallback */
{Unknown} / [^%] { stringBuf.append(yycharat(0)); }
{Unknown} { stringBuf.append(yycharat(0)); return rawString(); }
577 changes: 577 additions & 0 deletions core/src/main/java/org/jruby/lexer/StrptimeLexer.java

Large diffs are not rendered by default.

151 changes: 151 additions & 0 deletions core/src/main/java/org/jruby/util/RubyDateParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/***** 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-v10.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.
*
* 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.util;

import org.jruby.RubyBignum;
import org.jruby.RubyFixnum;
import org.jruby.RubyRational;
import org.jruby.RubyString;
import org.jruby.runtime.ThreadContext;

import java.util.HashMap;
import java.util.List;

import static org.jruby.util.StrptimeParser.FormatBag.has;

/**
* This class has {@code StrptimeParser} and provides methods that are calls from JRuby.
*/
public class RubyDateParser {
private final StrptimeParser strptimeParser;

public RubyDateParser() {
this.strptimeParser = new StrptimeParser();
}

/**
* Date._strptime method in JRuby 9.1.5.0's lib/ruby/stdlib/date/format.rb is replaced
* with this method. This is Java implementation of date__strptime method in MRI 2.3.1's
* ext/date/date_strptime.c.
* @see https://github.com/jruby/jruby/blob/036ce39f0476d4bd718e23e64caff36bb50b8dbc/lib/ruby/stdlib/date/format.rb
* @see https://github.com/ruby/ruby/blob/394fa89c67722d35bdda89f10c7de5c304a5efb1/ext/date/date_strptime.c
*/

public HashMap<String, Object> parse(ThreadContext context, final RubyString format, final RubyString text) {
final boolean tainted = text.isTaint();
final List<StrptimeToken> compiledPattern = strptimeParser.compilePattern(format.asJavaString());
final StrptimeParser.FormatBag bag = strptimeParser.parse(compiledPattern, text.asJavaString());
if (bag != null) {
return convertFormatBagToHash(context, bag, tainted);
} else {
return null;
}
}

private HashMap<String, Object> convertFormatBagToHash(ThreadContext context, StrptimeParser.FormatBag bag, boolean tainted) {
final HashMap<String, Object> map = new HashMap<>();

if (has(bag.getMDay())) {
map.put("mday", bag.getMDay());
}
if (has(bag.getWDay())) {
map.put("wday", bag.getWDay());
}
if (has(bag.getCWDay())) {
map.put("cwday", bag.getCWDay());
}
if (has(bag.getYDay())) {
map.put("yday", bag.getYDay());
}
if (has(bag.getCWeek())) {
map.put("cweek", bag.getCWeek());
}
if (has(bag.getCWYear())) {
map.put("cwyear", bag.getCWYear());
}
if (has(bag.getMin())) {
map.put("min", bag.getMin());
}
if (has(bag.getMon())) {
map.put("mon", bag.getMon());
}
if (has(bag.getHour())) {
map.put("hour", bag.getHour());
}
if (has(bag.getYear())) {
map.put("year", bag.getYear());
}
if (has(bag.getSec())) {
map.put("sec", bag.getSec());
}
if (has(bag.getWNum0())) {
map.put("wnum0", bag.getWNum0());
}
if (has(bag.getWNum1())) {
map.put("wnum1", bag.getWNum1());
}
if (bag.getZone() != null) {
final RubyString zone = RubyString.newString(context.getRuntime(), bag.getZone());
if (tainted) {
zone.taint(context);
}
map.put("zone", zone);
int offset = TimeZoneConverter.dateZoneToDiff(bag.getZone());
if (offset != Integer.MIN_VALUE) {
map.put("offset", offset);
}
}
if (has(bag.getSecFraction())) {
final RubyBignum secFraction = RubyBignum.newBignum(context.getRuntime(), bag.getSecFraction());
final RubyFixnum secFractionSize = RubyFixnum.newFixnum(context.getRuntime(), (long)Math.pow(10, bag.getSecFractionSize()));
map.put("sec_fraction", RubyRational.newRationalCanonicalize(context, secFraction, secFractionSize));
}
if (bag.has(bag.getSeconds())) {
if (has(bag.getSecondsSize())) {
final RubyBignum seconds = RubyBignum.newBignum(context.getRuntime(), bag.getSeconds());
final RubyFixnum secondsSize = RubyFixnum.newFixnum(context.getRuntime(), (long)Math.pow(10, bag.getSecondsSize()));
map.put("seconds", RubyRational.newRationalCanonicalize(context, seconds, secondsSize));
} else {
map.put("seconds", bag.getSeconds());
}
}
if (has(bag.getMerid())) {
map.put("_merid", bag.getMerid());
}
if (has(bag.getCent())) {
map.put("_cent", bag.getCent());
}
if (bag.getLeftover() != null) {
final RubyString leftover = RubyString.newString(context.getRuntime(), bag.getLeftover());
if (tainted) {
leftover.taint(context);
}
map.put("leftover", leftover);
}

return map;
}
}
70 changes: 70 additions & 0 deletions core/src/main/java/org/jruby/util/StrptimeFormat.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/***** 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-v10.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.
*
* 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.util;

/**
* This class is ported from RubyDateFormatter.Format in JRuby 9.1.5.0.
* @see https://github.com/jruby/jruby/blob/036ce39f0476d4bd718e23e64caff36bb50b8dbc/core/src/main/java/org/jruby/util/RubyDateFormatter.java
*/
enum StrptimeFormat {
FORMAT_STRING, // raw string, no formatting
FORMAT_SPECIAL, // composition of other formats

FORMAT_WEEK_LONG, // %A
FORMAT_WEEK_SHORT, // %a
FORMAT_MONTH_LONG, // %B
FORMAT_MONTH_SHORT, // %b, %h
FORMAT_CENTURY, // %C
FORMAT_DAY, // %d
FORMAT_DAY_S, // %e
FORMAT_WEEKYEAR, // %G
FORMAT_WEEKYEAR_SHORT, // %g
FORMAT_HOUR, // %H
FORMAT_HOUR_M, // %I
FORMAT_DAY_YEAR, // %j
FORMAT_HOUR_BLANK, // %k
FORMAT_MILLISEC, // %L
FORMAT_HOUR_S, // %l
FORMAT_MINUTES, // %M
FORMAT_MONTH, // %m
FORMAT_NANOSEC, // %N
FORMAT_MERIDIAN_LOWER_CASE, // %P
FORMAT_MERIDIAN, // %p
FORMAT_MICROSEC_EPOCH, // %Q Only for Date/DateTime from here
FORMAT_SECONDS, // %S
FORMAT_EPOCH, // %s
FORMAT_WEEK_YEAR_S, // %U
FORMAT_DAY_WEEK2, // %u
FORMAT_WEEK_WEEKYEAR, // %V
FORMAT_WEEK_YEAR_M, // %W
FORMAT_DAY_WEEK, // %w
FORMAT_YEAR_LONG, // %Y
FORMAT_YEAR_SHORT, // %y

FORMAT_COLON_ZONE_OFF, // %z, %:z, %::z, %:::z must be given number of colons as data

FORMAT_ZONE_ID; // %Z Change between Time and Date
}
823 changes: 823 additions & 0 deletions core/src/main/java/org/jruby/util/StrptimeParser.java

Large diffs are not rendered by default.

119 changes: 119 additions & 0 deletions core/src/main/java/org/jruby/util/StrptimeToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/***** 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-v10.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.
*
* 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.util;

import static org.jruby.util.StrptimeFormat.*;

/**
* This class is ported from RubyDateFormatter.Token in JRuby 9.1.5.0.
* @see https://github.com/jruby/jruby/blob/036ce39f0476d4bd718e23e64caff36bb50b8dbc/core/src/main/java/org/jruby/util/RubyDateFormatter.java
*/
public class StrptimeToken {
static final StrptimeToken[] CONVERSION2TOKEN = new StrptimeToken[256];

static {
CONVERSION2TOKEN['A'] = new StrptimeToken(FORMAT_WEEK_LONG);
CONVERSION2TOKEN['a'] = new StrptimeToken(FORMAT_WEEK_SHORT);
CONVERSION2TOKEN['B'] = new StrptimeToken(FORMAT_MONTH_LONG);
CONVERSION2TOKEN['b'] = new StrptimeToken(FORMAT_MONTH_SHORT);
CONVERSION2TOKEN['h'] = CONVERSION2TOKEN['b'];
CONVERSION2TOKEN['C'] = new StrptimeToken(FORMAT_CENTURY);
CONVERSION2TOKEN['d'] = new StrptimeToken(FORMAT_DAY);
CONVERSION2TOKEN['e'] = new StrptimeToken(FORMAT_DAY_S);
CONVERSION2TOKEN['G'] = new StrptimeToken(FORMAT_WEEKYEAR);
CONVERSION2TOKEN['g'] = new StrptimeToken(FORMAT_WEEKYEAR_SHORT);
CONVERSION2TOKEN['H'] = new StrptimeToken(FORMAT_HOUR);
CONVERSION2TOKEN['I'] = new StrptimeToken(FORMAT_HOUR_M);
CONVERSION2TOKEN['j'] = new StrptimeToken(FORMAT_DAY_YEAR);
CONVERSION2TOKEN['k'] = new StrptimeToken(FORMAT_HOUR_BLANK);
CONVERSION2TOKEN['L'] = new StrptimeToken(FORMAT_MILLISEC);
CONVERSION2TOKEN['l'] = new StrptimeToken(FORMAT_HOUR_S);
CONVERSION2TOKEN['M'] = new StrptimeToken(FORMAT_MINUTES);
CONVERSION2TOKEN['m'] = new StrptimeToken(FORMAT_MONTH);
CONVERSION2TOKEN['N'] = new StrptimeToken(FORMAT_NANOSEC);
CONVERSION2TOKEN['P'] = new StrptimeToken(FORMAT_MERIDIAN_LOWER_CASE);
CONVERSION2TOKEN['p'] = new StrptimeToken(FORMAT_MERIDIAN);
CONVERSION2TOKEN['Q'] = new StrptimeToken(FORMAT_MICROSEC_EPOCH);
CONVERSION2TOKEN['S'] = new StrptimeToken(FORMAT_SECONDS);
CONVERSION2TOKEN['s'] = new StrptimeToken(FORMAT_EPOCH);
CONVERSION2TOKEN['U'] = new StrptimeToken(FORMAT_WEEK_YEAR_S);
CONVERSION2TOKEN['u'] = new StrptimeToken(FORMAT_DAY_WEEK2);
CONVERSION2TOKEN['V'] = new StrptimeToken(FORMAT_WEEK_WEEKYEAR);
CONVERSION2TOKEN['W'] = new StrptimeToken(FORMAT_WEEK_YEAR_M);
CONVERSION2TOKEN['w'] = new StrptimeToken(FORMAT_DAY_WEEK);
CONVERSION2TOKEN['Y'] = new StrptimeToken(FORMAT_YEAR_LONG);
CONVERSION2TOKEN['y'] = new StrptimeToken(FORMAT_YEAR_SHORT);
}

private final StrptimeFormat format;
private final Object data;

StrptimeToken(StrptimeFormat format) {
this(format, null);
}

StrptimeToken(StrptimeFormat formatString, Object data) {
this.format = formatString;
this.data = data;
}

public static StrptimeToken str(String str) {
return new StrptimeToken(StrptimeFormat.FORMAT_STRING, str);
}

public static StrptimeToken format(char c) {
return CONVERSION2TOKEN[c];
}

public static StrptimeToken zoneOffsetColons(int colons) {
return new StrptimeToken(StrptimeFormat.FORMAT_COLON_ZONE_OFF, colons);
}

public static StrptimeToken special(char c) {
return new StrptimeToken(StrptimeFormat.FORMAT_SPECIAL, c);
}

/**
* Gets the data.
* @return Returns a Object
*/
Object getData() {
return data;
}

/**
* Gets the format.
* @return Returns a int
*/
StrptimeFormat getFormat() {
return format;
}

@Override
public String toString() {
return "<Token "+format+ " "+data+">";
}
}
474 changes: 474 additions & 0 deletions core/src/main/java/org/jruby/util/TimeZoneConverter.java

Large diffs are not rendered by default.

28 changes: 3 additions & 25 deletions lib/ruby/stdlib/date/format.rb
Original file line number Diff line number Diff line change
@@ -376,31 +376,9 @@ def self._strptime_i(str, fmt, e) # :nodoc:
private_class_method :_strptime_i

def self._strptime(str, fmt='%F')
str = str.dup
e = Format::Bag.new
return unless _strptime_i(str, fmt, e)

if e._cent
if e.cwyear
e.cwyear += e._cent * 100
end
if e.year
e. year += e._cent * 100
end
end

if e._merid
if e.hour
e.hour %= 12
e.hour += e._merid
end
end

unless str.empty?
e.leftover = str
end

e.to_hash
parser = org.jruby.util.RubyDateParser.new
map = parser.parse(JRuby.runtime.current_context, fmt, str)
return map.nil? ? nil : map.to_hash.inject({}){|hash,(k,v)| hash[k.to_sym] = v; hash}
end

def self.s3e(e, y, m, d, bc=false)