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. 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
/***** 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. * * 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 java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.math.BigInteger; import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Arrays; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jruby.lexer.StrptimeLexer;
This is Java implementation of ext/date/date_strptime.c in Ruby 2.3.1. see https://github.com/ruby/ruby/blob/394fa89c67722d35bdda89f10c7de5c304a5efb1/ext/date/date_strptime.c
/** * This is Java implementation of ext/date/date_strptime.c in Ruby 2.3.1. * see https://github.com/ruby/ruby/blob/394fa89c67722d35bdda89f10c7de5c304a5efb1/ext/date/date_strptime.c */
public class StrptimeParser { // day_names private static final String[] DAY_NAMES = new String[] { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; // month_names private static final String[] MONTH_NAMES = new String[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; // merid_names private static final String[] MERID_NAMES = new String[] { "am", "pm", "a.m.", "p.m." };
Ported Date::Format::Bag from lib/ruby/stdlib/date/format.rb in JRuby 9.1.5.0. see https://github.com/jruby/jruby/blob/036ce39f0476d4bd718e23e64caff36bb50b8dbc/lib/ruby/stdlib/date/format.rb
/** * Ported Date::Format::Bag from lib/ruby/stdlib/date/format.rb in JRuby 9.1.5.0. * see https://github.com/jruby/jruby/blob/036ce39f0476d4bd718e23e64caff36bb50b8dbc/lib/ruby/stdlib/date/format.rb */
public static class FormatBag { private int mDay = Integer.MIN_VALUE; private int wDay = Integer.MIN_VALUE; private int cWDay = Integer.MIN_VALUE; private int yDay = Integer.MIN_VALUE; private int cWeek = Integer.MIN_VALUE; private long cWYear = Long.MIN_VALUE; private int min = Integer.MIN_VALUE; private int mon = Integer.MIN_VALUE; private int hour = Integer.MIN_VALUE; private long year = Long.MIN_VALUE; private int sec = Integer.MIN_VALUE; private int wNum0 = Integer.MIN_VALUE; private int wNum1 = Integer.MIN_VALUE; private String zone = null; private Number secFraction = null; // Rational private int secFractionSize = Integer.MIN_VALUE; private Number seconds = null; // Bignum or Rational private int secondsSize = Integer.MIN_VALUE; private int merid = Integer.MIN_VALUE; private long cent = Long.MIN_VALUE; //private boolean fail = false; private String leftover = null; public int getMDay() { return mDay; } public int getWDay() { return wDay; } public int getCWDay() { return cWDay; } public int getYDay() { return yDay; } public int getCWeek() { return cWeek; } public long getCWYear() { return cWYear; } public int getMin() { return min; } public int getMon() { return mon; } public int getHour() { return hour; } public long getYear() { return year; } public int getSec() { return sec; } public int getWNum0() { return wNum0; } public int getWNum1() { return wNum1; } public String getZone() { return zone; } public Number getSecFraction() { return secFraction; } public int getSecFractionSize() { return secFractionSize; } public Number getSeconds() { return seconds; } public int getSecondsSize() { return secondsSize; } public int getMerid() { return merid; } public long getCent() { return cent; } public String getLeftover() { return leftover; } public static boolean has(int v) { return v != Integer.MIN_VALUE; } public static boolean has(long v) { return v != Long.MIN_VALUE; } public static boolean has(Number v) { return v != null; } }
Ported from RubyDateFormatter#addToPattern in JRuby 9.1.5.0. see https://github.com/jruby/jruby/blob/036ce39f0476d4bd718e23e64caff36bb50b8dbc/core/src/main/java/org/jruby/util/RubyDateFormatter.java
/** * Ported from RubyDateFormatter#addToPattern in JRuby 9.1.5.0. * see https://github.com/jruby/jruby/blob/036ce39f0476d4bd718e23e64caff36bb50b8dbc/core/src/main/java/org/jruby/util/RubyDateFormatter.java */
private void addToPattern(final List<StrptimeToken> compiledPattern, final String str) { for (int i = 0; i < str.length(); i++) { final char c = str.charAt(i); if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) { compiledPattern.add(StrptimeToken.format(c)); } else { compiledPattern.add(StrptimeToken.str(Character.toString(c))); } } }
Ported from RubyDateFormatter#compilePattern in JRuby 9.1.5.0. see https://github.com/jruby/jruby/blob/036ce39f0476d4bd718e23e64caff36bb50b8dbc/core/src/main/java/org/jruby/util/RubyDateFormatter.java
/** * Ported from RubyDateFormatter#compilePattern in JRuby 9.1.5.0. * see https://github.com/jruby/jruby/blob/036ce39f0476d4bd718e23e64caff36bb50b8dbc/core/src/main/java/org/jruby/util/RubyDateFormatter.java */
public List<StrptimeToken> compilePattern(final String pattern) { final List<StrptimeToken> compiledPattern = new LinkedList<>(); final Reader reader = new StringReader(pattern); // TODO Use try-with-resource statement StrptimeLexer lexer = new StrptimeLexer(reader); StrptimeToken token; try { while ((token = lexer.yylex()) != null) { if (token.getFormat() != StrptimeFormat.FORMAT_SPECIAL) { compiledPattern.add(token); } else { char c = (Character) token.getData(); switch (c) { case 'c': addToPattern(compiledPattern, "a b e H:M:S Y"); break; case 'D': case 'x': addToPattern(compiledPattern, "m/d/y"); break; case 'F': addToPattern(compiledPattern, "Y-m-d"); break; case 'n': compiledPattern.add(StrptimeToken.str("\n")); break; case 'R': addToPattern(compiledPattern, "H:M"); break; case 'r': addToPattern(compiledPattern, "I:M:S p"); break; case 'T': case 'X': addToPattern(compiledPattern, "H:M:S"); break; case 't': compiledPattern.add(StrptimeToken.str("\t")); break; case 'v': addToPattern(compiledPattern, "e-b-Y"); break; case 'Z': // +HH:MM in 'date', never zone name compiledPattern.add(StrptimeToken.zoneOffsetColons(1)); break; case '+': addToPattern(compiledPattern, "a b e H:M:S "); // %Z: +HH:MM in 'date', never zone name compiledPattern.add(StrptimeToken.zoneOffsetColons(1)); addToPattern(compiledPattern, " Y"); break; default: throw new Error("Unknown special char: " + c); } } } } catch (IOException e) { e.printStackTrace(); } return compiledPattern; } public FormatBag parse(final List<StrptimeToken> compiledPattern, final String text) { final FormatBag bag = new StringParser(text).parse(compiledPattern); if (bag == null) { return null; } if (FormatBag.has(bag.cent)) { if (FormatBag.has(bag.cWYear)) { bag.cWYear += bag.cent * 100; } if (FormatBag.has(bag.year)) { bag.year += bag.cent * 100; } // delete bag._cent bag.cent = Long.MIN_VALUE; } if (FormatBag.has(bag.merid)) { if (FormatBag.has(bag.hour)) { bag.hour %= 12; bag.hour += bag.merid; } // delete bag._merid bag.merid = Integer.MIN_VALUE; } return bag; } private static class StringParser { private static final Pattern ZONE_PARSE_REGEX = Pattern.compile("\\A(" + "(?:gmt|utc?)?[-+]\\d+(?:[,.:]\\d+(?::\\d+)?)?" + "|(?-i:[[\\p{Alpha}].\\s]+)(?:standard|daylight)\\s+time\\b" + "|(?-i:[[\\p{Alpha}]]+)(?:\\s+dst)?\\b" + ")", Pattern.CASE_INSENSITIVE); private final String text; private final FormatBag bag; private int pos; private boolean fail; private StringParser(String text) { this.text = text; this.bag = new FormatBag(); this.pos = 0; this.fail = false; } private FormatBag parse(final List<StrptimeToken> compiledPattern) { for (int tokenIndex = 0; tokenIndex < compiledPattern.size(); tokenIndex++) { final StrptimeToken token = compiledPattern.get(tokenIndex); switch (token.getFormat()) { case FORMAT_STRING: { final String str = token.getData().toString(); for (int i = 0; i < str.length(); i++) { final char c = str.charAt(i); if (isSpace(c)) { while (!isEndOfText(text, pos) && isSpace(text.charAt(pos))) { pos++; } } else { if (isEndOfText(text, pos) || c != text.charAt(pos)) { fail = true; } pos++; } } break; } case FORMAT_WEEK_LONG: // %A - The full weekday name (``Sunday'') case FORMAT_WEEK_SHORT: { // %a - The abbreviated name (``Sun'') final int dayIndex = findIndexInPatterns(DAY_NAMES); if (dayIndex >= 0) { bag.wDay = dayIndex % 7; pos += DAY_NAMES[dayIndex].length(); } else { fail = true; } break; } case FORMAT_MONTH_LONG: // %B - The full month name (``January'') case FORMAT_MONTH_SHORT: { // %b, %h - The abbreviated month name (``Jan'') final int monIndex = findIndexInPatterns(MONTH_NAMES); if (monIndex >= 0) { bag.mon = monIndex % 12 + 1; pos += MONTH_NAMES[monIndex].length(); } else { fail = true; } break; } case FORMAT_CENTURY: { // %C - year / 100 (round down. 20 in 2009) if (isNumberPattern(compiledPattern, tokenIndex)) { bag.cent = readDigits(2); } else { bag.cent = readDigitsMaxLong(); } break; } case FORMAT_DAY: // %d, %Od - Day of the month, zero-padded (01..31) case FORMAT_DAY_S: { // %e, %Oe - Day of the month, blank-padded ( 1..31) final long day; if (isBlank(text, pos)) { pos += 1; // blank day = readDigits(1); } else { day = readDigits(2); } if (!validRange(day, 1, 31)) { fail = true; } bag.mDay = (int)day; break; } case FORMAT_WEEKYEAR: { // %G - The week-based year if (isNumberPattern(compiledPattern, tokenIndex)) { bag.cWYear = readDigits(4); } else { bag.cWYear = readDigitsMaxLong(); } break; } case FORMAT_WEEKYEAR_SHORT: { // %g - The last 2 digits of the week-based year (00..99) final long v = readDigits(2); if (!validRange(v, 0, 99)) { fail = true; } bag.cWYear = v; if (!bag.has(bag.cent)) { bag.cent = v >= 69 ? 19 : 20; } break; } case FORMAT_HOUR: // %H, %OH - Hour of the day, 24-hour clock, zero-padded (00..23) case FORMAT_HOUR_BLANK: { // %k - Hour of the day, 24-hour clock, blank-padded ( 0..23) final long hour; if (isBlank(text, pos)) { pos += 1; // blank hour = readDigits(1); } else { hour = readDigits(2); } if (!validRange(hour, 0, 24)) { fail = true; } bag.hour = (int)hour; break; } case FORMAT_HOUR_M: // %I, %OI - Hour of the day, 12-hour clock, zero-padded (01..12) case FORMAT_HOUR_S: { // %l - Hour of the day, 12-hour clock, blank-padded ( 1..12) final long hour; if (isBlank(text, pos)) { pos += 1; // blank hour = readDigits(1); } else { hour = readDigits(2); } if (!validRange(hour, 1, 12)) { fail = true; } bag.hour = (int)hour; break; } case FORMAT_DAY_YEAR: { // %j - Day of the year (001..366) final long day = readDigits(3); if (!validRange(day, 1, 365)) { fail = true; } bag.yDay = (int)day; break; } case FORMAT_MILLISEC: // %L - Millisecond of the second (000..999) case FORMAT_NANOSEC: { // %N - Fractional seconds digits, default is 9 digits (nanosecond) boolean negative = false; if (isSign(text, pos)) { negative = text.charAt(pos) == '-'; pos++; } final Number v; final int initPos = pos; if (isNumberPattern(compiledPattern, tokenIndex)) { if (token.getFormat() == StrptimeFormat.FORMAT_MILLISEC) { v = BigInteger.valueOf(readDigits(3)); } else { v = BigInteger.valueOf(readDigits(9)); } } else { v = readDigitsMax(); } bag.secFraction = !negative ? v : negateInteger(v); bag.secFractionSize = pos - initPos; break; } case FORMAT_MINUTES: { // %M, %OM - Minute of the hour (00..59) final long min = readDigits(2); if (!validRange(min, 0, 59)) { fail = true; } bag.min = (int)min; break; } case FORMAT_MONTH: { // %m, %Om - Month of the year, zero-padded (01..12) final long mon = readDigits(2); if (!validRange(mon, 1, 12)) { fail = true; } bag.mon = (int)mon; break; } case FORMAT_MERIDIAN: // %P - Meridian indicator, lowercase (``am'' or ``pm'') case FORMAT_MERIDIAN_LOWER_CASE: { // %p - Meridian indicator, uppercase (``AM'' or ``PM'') final int meridIndex = findIndexInPatterns(MERID_NAMES); if (meridIndex >= 0) { bag.merid = meridIndex % 2 == 0 ? 0 : 12; pos += MERID_NAMES[meridIndex].length(); } else { fail = true; } break; } case FORMAT_MICROSEC_EPOCH: { // %Q - Number of microseconds since 1970-01-01 00:00:00 UTC. boolean negative = false; if (isMinus(text, pos)) { negative = true; pos++; } final Number sec = readDigitsMax(); bag.seconds = !negative ? sec : negateInteger(sec); bag.secondsSize = 3; break; } case FORMAT_SECONDS: { // %S - Second of the minute (00..59) final long sec = readDigits(2); if (!validRange(sec, 0, 60)) { fail = true; } bag.sec = (int)sec; break; } case FORMAT_EPOCH: { // %s - Number of seconds since 1970-01-01 00:00:00 UTC. boolean negative = false; if (isMinus(text, pos)) { negative = true; pos++; } final Number sec = readDigitsMax(); bag.seconds = !negative ? sec : negateInteger(sec); break; } case FORMAT_WEEK_YEAR_S: // %U, %OU - Week number of the year. The week starts with Sunday. (00..53) case FORMAT_WEEK_YEAR_M: { // %W, %OW - Week number of the year. The week starts with Monday. (00..53) final long week = readDigits(2); if (!validRange(week, 0, 53)) { fail = true; } if (token.getFormat() == StrptimeFormat.FORMAT_WEEK_YEAR_S) { bag.wNum0 = (int)week; } else { bag.wNum1 = (int)week; } break; } case FORMAT_DAY_WEEK2: { // %u, %Ou - Day of the week (Monday is 1, 1..7) final long day = readDigits(1); if (!validRange(day, 1, 7)) { fail = true; } bag.cWDay = (int)day; break; } case FORMAT_WEEK_WEEKYEAR: { // %V, %OV - Week number of the week-based year (01..53) final long week = readDigits(2); if (!validRange(week, 1, 53)) { fail = true; } bag.cWeek = (int)week; break; } case FORMAT_DAY_WEEK: { // %w - Day of the week (Sunday is 0, 0..6) final long day = readDigits(1); if (!validRange(day, 0, 6)) { fail = true; } bag.wDay = (int)day; break; } case FORMAT_YEAR_LONG: { // %Y, %EY - Year with century (can be negative, 4 digits at least) // -0001, 0000, 1995, 2009, 14292, etc. boolean negative = false; if (isSign(text, pos)) { negative = text.charAt(pos) == '-'; pos++; } final long year; if (isNumberPattern(compiledPattern, tokenIndex)) { year = readDigits(4); } else { year = readDigitsMaxLong(); } bag.year = !negative ? year : -1 * year; break; } case FORMAT_YEAR_SHORT: { // %y, %Ey, %Oy - year % 100 (00..99) final long year = readDigits(2); if (!validRange(year, 0, 99)) { fail = true; } bag.year = year; if (!bag.has(bag.cent)) { bag.cent = year >= 69 ? 19 : 20; } break; } case FORMAT_ZONE_ID: // %Z - Time zone abbreviation name case FORMAT_COLON_ZONE_OFF: { // %z - Time zone as hour and minute offset from UTC (e.g. +0900) // %:z - hour and minute offset from UTC with a colon (e.g. +09:00) // %::z - hour, minute and second offset from UTC (e.g. +09:00:00) // %:::z - hour, minute and second offset from UTC // (e.g. +09, +09:30, +09:30:30) if (isEndOfText(text, pos)) { fail = true; break; } final Matcher m = ZONE_PARSE_REGEX.matcher(text.substring(pos)); if (m.find()) { // zone String zone = text.substring(pos, pos + m.end()); bag.zone = zone; pos += zone.length(); } else { fail = true; } break; } case FORMAT_SPECIAL: { throw new Error("FORMAT_SPECIAL is a special token only for the lexer."); } } } if (fail) { return null; } if (text.length() > pos) { bag.leftover = text.substring(pos, text.length()); } return bag; }
Ports read_digits from ext/date/date_strptime.c in MRI 2.3.1 under BSDL. see https://github.com/ruby/ruby/blob/394fa89c67722d35bdda89f10c7de5c304a5efb1/ext/date/date_strftime.c
/** * Ports read_digits from ext/date/date_strptime.c in MRI 2.3.1 under BSDL. * see https://github.com/ruby/ruby/blob/394fa89c67722d35bdda89f10c7de5c304a5efb1/ext/date/date_strftime.c */
private long readDigits(final int len) { char c; long v = 0; final int initPos = pos; for (int i = 0; i < len; i++) { if (isEndOfText(text, pos)) { break; } c = text.charAt(pos); if (!isDigit(c)) { break; } else { v = v * 10 + toInt(c); } pos += 1; } if (pos == initPos) { fail = true; } return v; }
Ports READ_DIGITS_MAX from ext/date/date_strptime.c in MRI 2.3.1 under BSDL. see https://github.com/ruby/ruby/blob/394fa89c67722d35bdda89f10c7de5c304a5efb1/ext/date/date_strftime.c
Returns:integer value (Long or BigInteger)
/** * Ports READ_DIGITS_MAX from ext/date/date_strptime.c in MRI 2.3.1 under BSDL. * see https://github.com/ruby/ruby/blob/394fa89c67722d35bdda89f10c7de5c304a5efb1/ext/date/date_strftime.c * * @return integer value (Long or BigInteger) */
private Number readDigitsMax() { char c; long v = 0; BigInteger vBig = null; final int initPos = pos; while (true) { if (isEndOfText(text, pos)) break; c = text.charAt(pos); if (!isDigit(c)) break; if (vBig == null) { try { // Using 10L to avoid binary incompat with Java 8 (jruby/jruby#5451) long tmp = Math.multiplyExact(v, 10L); tmp = Math.addExact(tmp, toInt(c)); v = tmp; } catch (ArithmeticException overflow) { vBig = BigInteger.valueOf(v); continue; } } else { vBig = vBig.multiply(BigInteger.TEN); vBig = vBig.add(BigInteger.valueOf(toInt(c))); } pos += 1; } if (pos == initPos) { fail = true; } return vBig == null ? v : vBig; } private long readDigitsMaxLong() { char c; long v = 0L; final int initPos = pos; while (true) { if (isEndOfText(text, pos)) break; c = text.charAt(pos); if (!isDigit(c)) break; v = v * 10 + toInt(c); pos += 1; } if (pos == initPos) { fail = true; } return v; }
Returns -1 if text doesn't match with patterns.
/** * Returns -1 if text doesn't match with patterns. */
private int findIndexInPatterns(final String[] patterns) { if (isEndOfText(text, pos)) { return -1; } for (int i = 0; i < patterns.length; i++) { final String pattern = patterns[i]; final int len = pattern.length(); if (!isEndOfText(text, pos + len - 1) && pattern.equalsIgnoreCase(text.substring(pos, pos + len))) { // strncasecmp return i; } } return -1; // text doesn't match at any patterns. }
Ports num_pattern_p from ext/date/date_strptime.c in MRI 2.3.1 under BSDL. see https://github.com/ruby/ruby/blob/394fa89c67722d35bdda89f10c7de5c304a5efb1/ext/date/date_strftime.c
/** * Ports num_pattern_p from ext/date/date_strptime.c in MRI 2.3.1 under BSDL. * see https://github.com/ruby/ruby/blob/394fa89c67722d35bdda89f10c7de5c304a5efb1/ext/date/date_strftime.c */
private static boolean isNumberPattern(final List<StrptimeToken> compiledPattern, final int i) { if (compiledPattern.size() <= i + 1) { return false; } else { final StrptimeToken nextToken = compiledPattern.get(i + 1); final StrptimeFormat f = nextToken.getFormat(); if (f == StrptimeFormat.FORMAT_STRING && isDigit(((String) nextToken.getData()).charAt(0))) { return true; } else if (NUMBER_PATTERNS.contains(f)) { return true; } else { return false; } } } // CDdeFGgHIjkLlMmNQRrSsTUuVvWwXxYy private static final EnumSet<StrptimeFormat> NUMBER_PATTERNS = EnumSet.copyOf(Arrays.asList( StrptimeFormat.FORMAT_CENTURY, // 'C' // D StrptimeFormat.FORMAT_DAY, // 'd' StrptimeFormat.FORMAT_DAY_S, // 'e' // F StrptimeFormat.FORMAT_WEEKYEAR, // 'G' StrptimeFormat.FORMAT_WEEKYEAR_SHORT, // 'g' StrptimeFormat.FORMAT_HOUR, // 'H' StrptimeFormat.FORMAT_HOUR_M, // 'I' StrptimeFormat.FORMAT_DAY_YEAR, // 'j' StrptimeFormat.FORMAT_HOUR_BLANK, // 'k' StrptimeFormat.FORMAT_MILLISEC, // 'L' StrptimeFormat.FORMAT_HOUR_S, // 'l' StrptimeFormat.FORMAT_MINUTES, // 'M' StrptimeFormat.FORMAT_MONTH, // 'm' StrptimeFormat.FORMAT_NANOSEC, // 'N' // Q, R, r StrptimeFormat.FORMAT_SECONDS, // 'S' StrptimeFormat.FORMAT_EPOCH, // 's' // T StrptimeFormat.FORMAT_WEEK_YEAR_S, // 'U' StrptimeFormat.FORMAT_DAY_WEEK2, // 'u' StrptimeFormat.FORMAT_WEEK_WEEKYEAR, // 'V' // v StrptimeFormat.FORMAT_WEEK_YEAR_M, // 'W' StrptimeFormat.FORMAT_DAY_WEEK, // 'w' // X, x StrptimeFormat.FORMAT_YEAR_LONG, // 'Y' StrptimeFormat.FORMAT_YEAR_SHORT // 'y' ));
Ports valid_pattern_p in ext/date/date_strptime.c in MRI 2.3.1 under BSDL. see https://github.com/ruby/ruby/blob/394fa89c67722d35bdda89f10c7de5c304a5efb1/ext/date/date_strftime.c
/** * Ports valid_pattern_p in ext/date/date_strptime.c in MRI 2.3.1 under BSDL. * see https://github.com/ruby/ruby/blob/394fa89c67722d35bdda89f10c7de5c304a5efb1/ext/date/date_strftime.c */
private static boolean validRange(long v, int lower, int upper) { return lower <= v && v <= upper; } private static boolean isSpace(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\u000b' || c == '\f' || c == '\r'; } private static boolean isDigit(char c) { return '0' <= c && c <= '9'; } private static boolean isEndOfText(String text, int pos) { return pos >= text.length(); } private static boolean isSign(String text, int pos) { return !isEndOfText(text, pos) && (text.charAt(pos) == '+' || text.charAt(pos) == '-'); } private static boolean isMinus(String text, int pos) { return !isEndOfText(text, pos) && text.charAt(pos) == '-'; } private static boolean isBlank(String text, int pos) { return !isEndOfText(text, pos) && text.charAt(pos) == ' '; } private static int toInt(char c) { return c - '0'; } private static Number negateInteger(final Number i) { if (i instanceof BigInteger) { return ((BigInteger) i).negate(); } return -i.longValue(); } } }