Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
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. Copyright (C) 2002, 2009 Jan Arne Petersen <jpetersen@uni-bonn.de> Copyright (C) 2004 Charles O Nutter <headius@headius.com> Copyright (C) 2004 Anders Bengtsson <ndrsbngtssn@yahoo.se> Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de> 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.Reader;
 import java.util.Date;
 import java.util.List;
 
 
 import static org.jruby.util.RubyDateFormatter.FieldType.*;
 
 public class RubyDateFormatter {
     private static final DateFormatSymbols FORMAT_SYMBOLS = new DateFormatSymbols(.);
     private static final Token[] CONVERSION2TOKEN = new Token[256];
 
     private ThreadContext context;
     private StrftimeLexer lexer;
 
     static enum Format {
        
encoding to give to output
 
         FORMAT_ENCODING,
        
taint output
 
         FORMAT_TAINT,
        
raw string, no formatting
 
         FORMAT_STRING,
        
formatter
 
         FORMAT_OUTPUT,
        
composition of other formats, or depends on library
 
         FORMAT_SPECIAL,

        
%A
 
         FORMAT_WEEK_LONG('A'),
        
%a
 
         FORMAT_WEEK_SHORT('a'),
        
%B
 
         FORMAT_MONTH_LONG('B'),
        
%b, %h
 
         FORMAT_MONTH_SHORT('b''h'),
        
%C
 
         FORMAT_CENTURY('C'),
        
%d
 
         FORMAT_DAY('d'),
        
%e
 
         FORMAT_DAY_S('e'),
        
%G
 
         FORMAT_WEEKYEAR('G'),
        
%g
 
         FORMAT_WEEKYEAR_SHORT('g'),
        
%H
 
         FORMAT_HOUR('H'),
        
%I
 
        FORMAT_HOUR_M('I'),
        
%j
        FORMAT_DAY_YEAR('j'),
        
%k
        FORMAT_HOUR_BLANK('k'),
        
%L
        FORMAT_MILLISEC('L'),
        
%l
        FORMAT_HOUR_S('l'),
        
%M
        FORMAT_MINUTES('M'),
        
%m
        FORMAT_MONTH('m'),
        
%N
        FORMAT_NANOSEC('N'),
        
%P
        FORMAT_MERIDIAN_LOWER_CASE('P'),
        
%p
        FORMAT_MERIDIAN('p'),
        
%S
        FORMAT_SECONDS('S'),
        
%s
        FORMAT_EPOCH('s'),
        
%U
        FORMAT_WEEK_YEAR_S('U'),
        
%u
        FORMAT_DAY_WEEK2('u'),
        
%V
        FORMAT_WEEK_WEEKYEAR('V'),
        
%W
        FORMAT_WEEK_YEAR_M('W'),
        
%w
        FORMAT_DAY_WEEK('w'),
        
%Y
        FORMAT_YEAR_LONG('Y'),
        
%y
        FORMAT_YEAR_SHORT('y'),
        
%z, %:z, %::z, %:::z
        FORMAT_COLON_ZONE_OFF, // must be given number of colons as data
        /* Change between Time and Date */
        
%Z
        FORMAT_ZONE_ID,
        /* Only for Date/DateTime from here */
        
%Q
        FORMAT_MICROSEC_EPOCH;
        Format() {}
        Format(char conversion) {
            [conversion] = new Token(this);
        }
        Format(char conversionchar alias) {
            this(conversion);
            [alias] = [conversion];
        }
    }
    static final Format INSTANTIATE_ENUM = .;
    public static void main(String[] args) {
        // composed + special, keys of the switch below
        StringBuilder buf = new StringBuilder("cDxFnQRrTXtvZ+z");
        for (int i = 'A'i <= 'z'i++) {
            if ([i] != null) {
                buf.append((chari);
            }
        }
        ..println(buf.toString());
    }
    public static class Token {
        private final Format format;
        private final Object data;
        
        protected Token(Format format) {
            this(formatnull);
        }
        protected Token(Format formatStringObject data) {
            this. = formatString;
            this. = data;
        }
        public static Token str(String str) {
            return new Token(.str);
        }
        public static Token format(char c) {
            return [c];
        }
        public static Token zoneOffsetColons(int colons) {
            return new Token(.colons);
        }
        public static Token special(char c) {
            return new Token(.c);
        }
        public static Token formatter(RubyTimeOutputFormatter formatter) {
            return new Token(.formatter);
        }

        
Gets the data.

Returns:
Returns a Object
        public Object getData() {
            return ;
        }

        
Gets the format.

Returns:
Returns a int
        public Format getFormat() {
            return ;
        }
        @Override
        public String toString() {
            return "<Token "+" "++">";
        }
    }

    
Constructor for RubyDateFormatter.
    public RubyDateFormatter(ThreadContext context) {
        super();
        this. = context;
         = new StrftimeLexer((Readernull);
    }
    private void addToPattern(List<TokencompiledPatternString str) {
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) {
                compiledPattern.add(Token.format(c));
            } else {
                compiledPattern.add(Token.str(Character.toString(c)));
            }
        }
    }
    public List<TokencompilePattern(RubyString formatboolean dateLibrary) {
        ByteList pattern = format.getByteList();
        List<TokencompiledPattern = new LinkedList<Token>();
        Encoding enc = pattern.getEncoding();
        if (!enc.isAsciiCompatible()) {
            throw ..newArgumentError("format should have ASCII compatible encoding");
        }
        if (enc != .) { // default for ByteList
            compiledPattern.add(new Token(.enc));
        }
        if (format.isTaint()) {
            compiledPattern.add(new Token(.));
        }
        ByteArrayInputStream in = new ByteArrayInputStream(pattern.getUnsafeBytes(), pattern.getBegin(), pattern.getRealSize());
        Reader reader = new InputStreamReader(in..getEncodingService().charsetForEncoding(pattern.getEncoding()));
        .yyreset(reader);
        Token token;
        try {
            while ((token = .yylex()) != null) {
                if (token.format != .) {
                    compiledPattern.add(token);
                } else {
                    char c = (Charactertoken.data;
                    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(Token.str("\n"));
                        break;
                    case 'Q':
                        if (dateLibrary) {
                            compiledPattern.add(new Token(.));
                        } else {
                            compiledPattern.add(Token.str("%Q"));
                        }
                        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(Token.str("\t"));
                        break;
                    case 'v':
                        addToPattern(compiledPattern"e-");
                        if (!dateLibrary)
                            compiledPattern.add(Token.formatter(new RubyTimeOutputFormatter("^", 0)));
                        addToPattern(compiledPattern"b-Y");
                        break;
                    case 'Z':
                        if (dateLibrary) {
                            // +HH:MM in 'date', never zone name
                            compiledPattern.add(Token.zoneOffsetColons(1));
                        } else {
                            compiledPattern.add(new Token(.));
                        }
                        break;
                    case '+':
                        if (!dateLibrary) {
                            compiledPattern.add(Token.str("%+"));
                            break;
                        }
                        addToPattern(compiledPattern"a b e H:M:S ");
                        // %Z: +HH:MM in 'date', never zone name
                        compiledPattern.add(Token.zoneOffsetColons(1));
                        addToPattern(compiledPattern" Y");
                        break;
                    default:
                        throw new Error("Unknown special char: "+c);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return compiledPattern;
    }
    static enum FieldType {
        NUMERIC('0', 0),
        NUMERIC2('0', 2),
        NUMERIC2BLANK(' ', 2),
        NUMERIC3('0', 3),
        NUMERIC4('0', 4),
        NUMERIC5('0', 5),
        TEXT(' ', 0);
        char defaultPadder;
        int defaultWidth;
        FieldType(char padderint width) {
             = padder;
             = width;
        }
    }

    
Convenience method when using no pattern caching
    public RubyString compileAndFormat(RubyString patternboolean dateLibraryDateTime dtlong nsecIRubyObject sub_millis) {
        return format(compilePattern(patterndateLibrary), dtnsecsub_millis);
    }
    public RubyString format(List<TokencompiledPatternDateTime dtlong nsecIRubyObject sub_millis) {
        ByteList toAppendTo = new ByteList();
        boolean taint = false;
        for (Token tokencompiledPattern) {
            String output = null;
            long value = 0;
            FieldType type = ;
            Format format = token.getFormat();
            switch (format) {
                case :
                    toAppendTo.setEncoding((Encodingtoken.getData());
                    continue// go to next token
                case :
                    taint = true;
                    continue// go to next token
                case :
                    formatter = (RubyTimeOutputFormattertoken.getData();
                    continue// go to next token
                case :
                    output = token.getData().toString();
                    break;
                case :
                    // This is GROSS, but Java API's aren't ISO 8601 compliant at all
                    int v = (dt.getDayOfWeek()+1)%8;
                    if(v == 0) {
                        v++;
                    }
                    output = .getWeekdays()[v];
                    break;
                case :
                    // This is GROSS, but Java API's aren't ISO 8601 compliant at all
                    v = (dt.getDayOfWeek()+1)%8;
                    if(v == 0) {
                        v++;
                    }
                    output = .getShortWeekdays()[v];
                    break;
                case :
                    output = .getMonths()[dt.getMonthOfYear()-1];
                    break;
                case :
                    output = .getShortMonths()[dt.getMonthOfYear()-1];
                    break;
                case :
                    type = ;
                    value = dt.getDayOfMonth();
                    break;
                case :
                    type = ;
                    value = dt.getDayOfMonth();
                    break;
                case :
                    type = ;
                    value = dt.getHourOfDay();
                    break;
                case :
                    type = ;
                    value = dt.getHourOfDay();
                    break;
                case :
                case :
                    value = dt.getHourOfDay();
                    if (value == 0) {
                        value = 12;
                    } else if (value > 12) {
                        value -= 12;
                    }
                    type = (format == .) ?  : ;
                    break;
                case :
                    type = ;
                    value = dt.getDayOfYear();
                    break;
                case :
                    type = ;
                    value = dt.getMinuteOfHour();
                    break;
                case :
                    type = ;
                    value = dt.getMonthOfYear();
                    break;
                case :
                    output = dt.getHourOfDay() < 12 ? "AM" : "PM";
                    break;
                case :
                    output = dt.getHourOfDay() < 12 ? "am" : "pm";
                    break;
                case :
                    type = ;
                    value = dt.getSecondOfMinute();
                    break;
                case :
                    type = ;
                    value = formatWeekYear(dt...);
                    break;
                case :
                    type = ;
                    value = formatWeekYear(dt...);
                    break;
                case :
                    type = ;
                    value = dt.getDayOfWeek() % 7;
                    break;
                case :
                    type = ;
                    value = dt.getDayOfWeek();
                    break;
                case :
                    value = year(dtdt.getYear());
                    type = (value >= 0) ?  : ;
                    break;
                case :
                    type = ;
                    value = year(dtdt.getYear()) % 100;
                    break;
                case :
                    // custom logic because this is so weird
                    value = dt.getZone().getOffset(dt.getMillis()) / 1000;
                    int colons = (Integertoken.getData();
                    output = formatZone(colons, (intvalueformatter);
                    break;
                case :
                    output = dt.getZone().getShortName(dt.getMillis());
                    break;
                case :
                    type = ;
                    value = year(dtdt.getYear()) / 100;
                    break;
                case :
                    type = ;
                    value = dt.getMillis() / 1000;
                    break;
                case :
                    type = ;
                    value = dt.getWeekOfWeekyear();
                    break;
                case :
                case :
                    int defaultWidth = (format == .) ? 9 : 3;
                    int width = formatter.getWidth(defaultWidth);
                    output = RubyTimeOutputFormatter.formatNumber(dt.getMillisOfSecond(), 3, '0');
                    if (width > 3) {
                        if (sub_millis == null || sub_millis.isNil()) { // Time
                            output += RubyTimeOutputFormatter.formatNumber(nsec, 6, '0');
                        } else { // Date, DateTime
                            int prec = width - 3;
                            IRubyObject power = ..newFixnum(10).callMethod("**"..newFixnum(prec));
                            IRubyObject truncated = sub_millis.callMethod("numerator").callMethod("*"power);
                            truncated = truncated.callMethod("/"sub_millis.callMethod("denominator"));
                            long decimals = truncated.convertToInteger().getLongValue();
                            output += RubyTimeOutputFormatter.formatNumber(decimalsprec'0');
                        }
                    }
                    if (width < output.length()) {
                        output = output.substring(0, width);
                    } else {
                        // Not enough precision, fill with 0
                        while(output.length() < width)
                            output += "0";
                    }
                    formatter = .// no more formatting
                    break;
                case :
                    value = year(dtdt.getWeekyear());
                    type = (value >= 0) ?  : ;
                    break;
                case :
                    type = ;
                    value = year(dtdt.getWeekyear()) % 100;
                    break;
                case :
                    // only available for Date
                    type = ;
                    value = dt.getMillis();
                    break;
                case :
                    throw new Error("FORMAT_SPECIAL is a special token only for the lexer.");
            }
            output = formatter.format(outputvaluetype);
            // reset formatter
            formatter = .;
            toAppendTo.append(output.getBytes(..getEncodingService().charsetForEncoding(toAppendTo.getEncoding())));
        }
        RubyString str = ..newString(toAppendTo);
        if (taint)
            str.taint();
        return str;
    }

    
Ruby always follows Astronomical year numbering, that is BC x is -x+1 and there is a year 0 (BC 1) but Joda-time returns -x for year x BC in Julian chronology (no year 0)
    private int year(DateTime dtint year) {
        Chronology c;
        if (year < 0 && (
                (c = dt.getChronology()) instanceof JulianChronology ||
                (c instanceof GJChronology && ((GJChronologyc).getGregorianCutover().isAfter(dt))))
            return year + 1;
        return year;
    }
    private int formatWeekYear(DateTime dtint firstDayOfWeek) {
        java.util.Calendar dtCalendar = dt.toGregorianCalendar();
        dtCalendar.setFirstDayOfWeek(firstDayOfWeek);
        dtCalendar.setMinimalDaysInFirstWeek(7);
        int value = dtCalendar.get(...);
        if ((value == 52 || value == 53) &&
                (dtCalendar.get(.) == . )) {
            // MRI behavior: Week values are monotonous.
            // So, weeks that effectively belong to previous year,
            // will get the value of 0, not 52 or 53, as in Java.
            value = 0;
        }
        return value;
    }
    private String formatZone(int colonsint valueRubyTimeOutputFormatter formatter) {
        int seconds = Math.abs(value);
        int hours = seconds / 3600;
        seconds %= 3600;
        int minutes = seconds / 60;
        seconds %= 60;
        if (value < 0 && hours != 0) { // see below when hours == 0
            hours = -hours;
        }
        String mm = RubyTimeOutputFormatter.formatNumber(minutes, 2, '0');
        String ss = RubyTimeOutputFormatter.formatNumber(seconds, 2, '0');
        char padder = formatter.getPadder('0');
        int defaultWidth = -1;
        String after = null;
        switch (colons) {
            case 0: // %z -> +hhmm
                defaultWidth = 5;
                after = mm;
                break;
            case 1: // %:z -> +hh:mm
                defaultWidth = 6;
                after = ":" + mm;
                break;
            case 2: // %::z -> +hh:mm:ss
                defaultWidth = 9;
                after = ":" + mm + ":" + ss;
                break;
            case 3: // %:::z -> +hh[:mm[:ss]]
                if (minutes == 0) {
                    if (seconds == 0) { // +hh
                        defaultWidth = 3;
                        after = "";
                    } else { // +hh:mm
                        return formatZone(1, valueformatter);
                    }
                } else { // +hh:mm:ss
                    return formatZone(2, valueformatter);
                }
                break;
        }
        int minWidth = defaultWidth - 1;
        int width = formatter.getWidth(defaultWidth);
        if (width < minWidth) {
            width = minWidth;
        }
        width -= after.length();
        String before = RubyTimeOutputFormatter.formatSignedNumber(hourswidthpadder);
        if (value < 0 && hours == 0) // the formatter could not handle this case
            before = before.replace('+''-');
        return before + after;
    }

    
    public Date parse(String sourceParsePosition pos) {
        throw new UnsupportedOperationException();
    }
New to GrepCode? Check out our FAQ X