org.renjin.primitives.time.Time.java Source code

Java tutorial

Introduction

Here is the source code for org.renjin.primitives.time.Time.java

Source

/*
 * R : A Computer Language for Statistical Data Analysis
 * Copyright (C) 1995, 1996  Robert Gentleman and Ross Ihaka
 * Copyright (C) 1997--2008  The R Development Core Team
 * Copyright (C) 2003, 2004  The R Foundation
 * Copyright (C) 2010 bedatadriven
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.renjin.primitives.time;

import com.google.common.base.Strings;
import org.joda.time.*;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeParser;
import org.joda.time.format.DateTimeParserBucket;
import org.renjin.invoke.annotations.Internal;
import org.renjin.sexp.*;

import java.util.List;
import java.util.Locale;

/**
 * Implementation of date time-related functions.
 * 
 * <p>
 * R has three representations of date/times:
 * 
 * <ul>
 * <li>POSIXct - which is a stored as DoubleVector with the number seconds since Jan 1st 1970, with 
 * classes "POSIXct" and "POSIXt"</li>
 * <li>POSIXlt - which is stored as ListVector containing calendar elements sec, min, mon, etc</li>
 * <li>Date - which is stored as a DoubleVector with the number of days since Jan 1st 1970, where
 * Jan 1st, 1970 is day zero.</li>
 * </ul>
 */
public class Time {

    public static DateTime EPOCH = new DateTime(1970, 1, 1, 0, 0, 0);

    /**
     * Parses a string value into a date time value.
     * @return
     */
    @Internal
    public static SEXP strptime(StringVector x, StringVector formats, String tz) {

        if (x.length() == 0 || formats.length() == 0) {
            return StringVector.EMPTY;
        }

        DateTimeZone timeZone = timeZoneFromRSpecification(tz);

        List<DateTimeFormatter> formatters = DateTimeFormat.forPatterns(formats, timeZone, false);

        PosixLtVector.Builder result = new PosixLtVector.Builder();
        int resultLength = Math.max(x.length(), formats.length());
        for (int i = 0; i != resultLength; ++i) {
            DateTimeFormatter formatter = formatters.get(i % formatters.size());
            String string = x.getElementAsString(i % x.length());
            try {
                result.add(parseIgnoreTrailingCharacters(formatter, string));
            } catch (IllegalArgumentException e) {
                result.addNA();
            }
        }
        if (!Strings.isNullOrEmpty(tz)) {
            result.withTimeZone(timeZone);
        }
        return result.buildListVector();
    }

    private static DateTime parseIgnoreTrailingCharacters(DateTimeFormatter formatter, String text) {
        // this is a modified version of DateTimeFormatter.parseDateTime() that does not
        // throw an exception on trailing characters

        Chronology chronology = DateTimeUtils.getChronology(null);
        DateTimeParser parser = formatter.getParser();

        Locale locale = null;
        Integer pivotYear = null;
        int defaultYear = 2000;
        DateTimeZone timeZone = null;

        DateTimeParserBucket bucket = new DateTimeParserBucket(0, chronology, locale, pivotYear, defaultYear);
        int newPos = parser.parseInto(bucket, text, 0);
        if (newPos >= 0) {
            long millis = bucket.computeMillis(true, text);
            if (formatter.isOffsetParsed() && bucket.getOffsetInteger() != null) {
                int parsedOffset = bucket.getOffsetInteger();
                DateTimeZone parsedZone = DateTimeZone.forOffsetMillis(parsedOffset);
                chronology = chronology.withZone(parsedZone);
            } else if (bucket.getZone() != null) {
                chronology = chronology.withZone(bucket.getZone());
            }
            DateTime dt = new DateTime(millis, chronology);
            if (timeZone != null) {
                dt = dt.withZone(timeZone);
            }
            return dt;
        }
        throw new IllegalArgumentException();
    }

    /**
     * Converts a calendar-based representation of time (POSIXlt: see above) to 
     * a unix time value.
     * 
     * @param x a ListVector containing the fields above
     */
    @Internal("as.POSIXct")
    public static DoubleVector asPOSIXct(ListVector x, String tz) {
        return new PosixCtVector.Builder().addAll(new PosixLtVector(x)).buildDoubleVector();
    }

    @Internal("as.POSIXlt")
    public static ListVector asPOSIXlt(DoubleVector x, String tz) {
        return new PosixLtVector.Builder().addAll(new PosixCtVector(x)).buildListVector();
    }

    /**
     * Converts a POSIXlt object (in calendar form) to a Date object,
     * which stores dates as an offset from Jan 1, 1970.
     */
    @Internal
    public static DoubleVector POSIXlt2Date(ListVector x) {
        PosixLtVector ltVector = new PosixLtVector(x);
        DoubleArrayVector.Builder dateVector = DoubleArrayVector.Builder.withInitialCapacity(ltVector.length());
        for (int i = 0; i != ltVector.length(); ++i) {
            DateTime date = ltVector.getElementAsDateTime(i);
            dateVector.add(Days.daysBetween(EPOCH, date).getDays());
        }
        dateVector.setAttribute(Symbols.CLASS, StringVector.valueOf("Date"));
        return dateVector.build();
    }

    @Internal
    public static ListVector Date2POSIXlt(DoubleVector x) {
        PosixLtVector.Builder ltVector = new PosixLtVector.Builder();
        for (int i = 0; i != x.length(); ++i) {
            int daysSinceEpoch = x.getElementAsInt(i);
            ltVector.add(EPOCH.plusDays(daysSinceEpoch));
        }
        return ltVector.buildListVector();
    }

    @Internal("Sys.time")
    public static DoubleVector sysTime() {
        return new PosixCtVector.Builder().add(new DateTime()).buildDoubleVector();
    }

    /**
     * Formats a Posix-lt time as a string.
     */
    @Internal("format.POSIXlt")
    public static StringVector formatPOSIXlt(ListVector x, StringVector patterns, boolean useTz) {

        PosixLtVector dateTimes = new PosixLtVector(x);
        List<DateTimeFormatter> formatters = DateTimeFormat.forPatterns(patterns, DateTimeZone.getDefault(), useTz);

        StringVector.Builder result = new StringVector.Builder();
        int resultLength = Math.max(dateTimes.length(), patterns.length());

        for (int i = 0; i != resultLength; ++i) {
            DateTimeFormatter formatter = formatters.get(i % formatters.size());
            DateTime dateTime = dateTimes.getElementAsDateTime(i % dateTimes.length());

            result.add(formatter.print(dateTime));
        }

        return result.build();
    }

    /**
     * Creates a Joda {@link DateTimeZone} instance from an R timezone string.
     */
    public static DateTimeZone timeZoneFromRSpecification(String tz) {
        if (Strings.isNullOrEmpty(tz)) {
            return DateTimeZone.getDefault();
        } else if ("GMT".equals(tz)) {
            return DateTimeZone.UTC;
        } else {
            // TODO: this probably isn't right..
            return DateTimeZone.forID(tz);
        }
    }

    /**
     * Creates a Joda {@link DateTimeZone} instance from the {@code tzone}
     * attribute of an R Posix object. Returns the current timezone if
     * there is no {@code tzone} attribute;
     */
    public static DateTimeZone timeZoneFromPosixObject(SEXP lt) {
        SEXP attribute = lt.getAttribute(Symbols.TZONE);
        if (attribute instanceof StringVector) {
            return timeZoneFromRSpecification(((StringVector) attribute).getElementAsString(0));
        } else {
            return DateTimeZone.getDefault();
        }
    }
}