org.opendatakit.common.android.utilities.DataUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.opendatakit.common.android.utilities.DataUtil.java

Source

/*
 * Copyright (C) 2012 University of Washington
 *
 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.opendatakit.common.android.utilities;

import java.util.ArrayList;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Interval;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;
import org.opendatakit.aggregate.odktables.rest.TableConstants;

public class DataUtil {

    private static final String TAG = DataUtil.class.getSimpleName();

    private static final String[] USER_FULL_DATETIME_PATTERNS = { "yyyy-MM-dd'T'HH:mm:ss.SSSZ", // ODK Collect format
            "M/d/yy h:mm:ssa", "M/d/yy HH:mm:ss", "M/d/yyyy h:mm:ssa", "M/d/yyyy HH:mm:ss", "M/d h:mm:ssa",
            "M/d HH:mm:ss", "d h:mm:ssa", "d HH:mm:ss", "E h:mm:ssa", "E HH:mm:ss", "HH:mm:ss.SSSZ" // ODK Collect format for time
    };
    private static final String[][] USER_PARTIAL_DATETIME_PATTERNS = { {
            // minute
            "M/d/yy h:mma", "M/d/yy HH:mm", "M/d/yyyy h:mma", "M/d/yyyy HH:mm", "M/d h:mma", "M/d HH:mm", "d h:mma",
            "d HH:mm", "E h:mma", "E HH:mm" },
            {
                    // hour
                    "M/d/yy ha", "M/d/yy HH", "M/d/yyyy ha", "M/d/yyyy HH", "M/d ha", "M/d HH", "d ha", "d HH",
                    "E ha", "E HH" },
            {
                    // day
                    "yyyy-MM-dd", // ODK Collect format for date
                    "M/d/yy", "M/d/yyyy", "M/d", "d", "E" } };
    private static final int[] USER_INTERVAL_DURATIONS = { 60, 3600, 86400 };

    private static final Pattern USER_DURATION_FORMAT = Pattern.compile("(\\d+)(s|m|h|d)");

    private static final Pattern USER_NOW_RELATIVE_FORMAT = Pattern.compile("^now\\s*(-|\\+)\\s*(\\d+(s|m|h|d))$");

    private static final Pattern USER_LOCATION_LAT_LON_FORMAT = Pattern
            .compile("^\\s*\\(?(-?(\\d+|\\.\\d+|\\d+\\.\\d+))," + "\\s*(-?(\\d+|\\.\\d+|\\d+\\.\\d+))\\)?\\s*$");
    private static final Pattern USER_LOCATION_LAT_LON_ALT_ACC_FORMAT = Pattern
            .compile("^\\s*\\(?(-?(\\d+|\\.\\d+|\\d+\\.\\d+))" + "\\s+(-?(\\d+|\\.\\d+|\\d+\\.\\d+))"
                    + "(\\s+(-?(\\d+|\\.\\d+|\\d+\\.\\d+))" + "\\s+(-?(\\d+|\\.\\d+|\\d+\\.\\d+)))?" + "\\)?\\s*$");
    private static final Pattern USER_LOCATION_UTM_COMMA_FORMAT = Pattern.compile(
            "(-?(\\d+|\\.\\d+|\\d+\\.\\d+)),\\s*" + "(-?(\\d+|\\.\\d+|\\d+\\.\\d+)),\\s*(\\d+),\\s*(n|N|s|S)");
    private static final Pattern USER_LOCATION_UTM_SPACE_FORMAT = Pattern
            .compile("(\\d+)(n|N|s|S)\\s+(-?(\\d+|\\.\\d+|\\d+\\.\\d+))" + "\\s+(-?(\\d+|\\.\\d+|\\d+\\.\\d+))");

    private static final String USER_SHORT_FORMAT = "M/d h:mma";
    private static final String USER_LONG_FORMAT = "M/d/yyyy h:mm:ssa";

    private final Locale locale;
    private final DateTimeZone tz;
    private final DateTimeFormatter userFullParser;
    private final DateTimeFormatter[] userPartialParsers;
    private final DateTimeFormatter userShortFormatter;
    private final DateTimeFormatter userLongFormatter;

    public DataUtil(Locale locale, TimeZone tz) {
        this.locale = locale;
        this.tz = DateTimeZone.forTimeZone(tz);
        DateTimeFormatterBuilder fpBuilder = new DateTimeFormatterBuilder();
        for (String pattern : USER_FULL_DATETIME_PATTERNS) {
            DateTimeFormatter f = DateTimeFormat.forPattern(pattern);
            fpBuilder.appendOptional(f.getParser());
        }
        userFullParser = fpBuilder.toFormatter().withLocale(locale).withZone(this.tz);
        userPartialParsers = new DateTimeFormatter[USER_PARTIAL_DATETIME_PATTERNS.length];
        for (int i = 0; i < USER_PARTIAL_DATETIME_PATTERNS.length; i++) {
            DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder();
            for (String pattern : USER_PARTIAL_DATETIME_PATTERNS[i]) {
                DateTimeFormatter f = DateTimeFormat.forPattern(pattern);
                dtfb.appendOptional(f.getParser());
            }
            userPartialParsers[i] = dtfb.toFormatter().withLocale(locale).withZone(this.tz);
        }
        userShortFormatter = DateTimeFormat.forPattern(USER_SHORT_FORMAT);
        userLongFormatter = DateTimeFormat.forPattern(USER_LONG_FORMAT);
    }

    public String validifyDateValue(String input) {
        DateTime instant = tryParseInstant(input);
        if (instant != null) {
            return formatDateTimeForDb(instant);
        }
        Interval interval = tryParseInterval(input);
        if (interval != null) {
            return formatDateTimeForDb(interval.getStart());
        }
        return null;
    }

    public String validifyDateTimeValue(String input) {
        DateTime instant = tryParseInstant(input);
        if (instant != null) {
            return formatDateTimeForDb(instant);
        }
        Interval interval = tryParseInterval(input);
        if (interval != null) {
            return formatDateTimeForDb(interval.getStart());
        }
        return null;
    }

    public String validifyTimeValue(String input) {
        // TODO: does this need to be different?
        // Need to respect TimeZone. What should Date be?
        DateTime instant = tryParseInstant(input);
        if (instant != null) {
            return formatDateTimeForDb(instant);
        }
        Interval interval = tryParseInterval(input);
        if (interval != null) {
            return formatDateTimeForDb(interval.getStart());
        }
        return null;
    }

    public String validifyDateRangeValue(String input) {
        Interval interval = tryParseInterval(input);
        if (interval != null) {
            return formatIntervalForDb(interval);
        }
        return null;
    }

    public String validifyNumberValue(String input) {
        if (input == null || input.length() == 0) {
            return null;
        }
        try {
            Double.parseDouble(input);
            return input;
        } catch (NumberFormatException e) {
            return null;
        }
    }

    public String validifyIntegerValue(String input) {
        if (input == null || input.length() == 0) {
            return null;
        }
        try {
            Integer.parseInt(input);
            return input;
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private String validifyMultipleChoiceValue(ArrayList<String> choices, String input) {
        for (String opt : choices) {
            if (opt.equalsIgnoreCase(input)) {
                return opt;
            }
        }
        return null;
    }

    private String validifyLocationValue(String input) {
        if (input == null) {
            return null;
        }
        // free-form "lat, long" string entered by user
        Matcher matcher = USER_LOCATION_LAT_LON_FORMAT.matcher(input);
        if (matcher.matches()) {
            return matcher.group(1) + "," + matcher.group(3);
        }
        // "lat long alt acc" from ODK Collect
        matcher = USER_LOCATION_LAT_LON_ALT_ACC_FORMAT.matcher(input);
        if (matcher.matches()) {
            return matcher.group(1) + "," + matcher.group(3);
        }
        // UTM coordinates "630084,4833438,17,N"
        matcher = USER_LOCATION_UTM_COMMA_FORMAT.matcher(input);
        if (matcher.matches()) {
            double x = Double.parseDouble(matcher.group(1));
            double y = Double.parseDouble(matcher.group(3));
            int zone = Integer.parseInt(matcher.group(5));
            String hemi = matcher.group(6);
            boolean isSouthHemi = hemi.equalsIgnoreCase("s");
            double[] latLon = UTMConverter.parseUTM(x, y, zone, isSouthHemi);
            if (latLon == null) {
                return null;
            }
            String latStr = String.format("%.5g", latLon[0]);
            String lonStr = String.format("%.5g", latLon[1]);
            return latStr + "," + lonStr;
        }
        // UTM coordinates "17N 630084 4833438"
        matcher = USER_LOCATION_UTM_SPACE_FORMAT.matcher(input);
        if (matcher.matches()) {
            double x = Double.parseDouble(matcher.group(3));
            double y = Double.parseDouble(matcher.group(5));
            int zone = Integer.parseInt(matcher.group(1));
            String hemi = matcher.group(2);
            boolean isSouthHemi = hemi.equalsIgnoreCase("s");
            double[] latLon = UTMConverter.parseUTM(x, y, zone, isSouthHemi);
            if (latLon == null) {
                return null;
            }
            String latStr = String.format("%.5g", latLon[0]);
            String lonStr = String.format("%.5g", latLon[1]);
            return latStr + "," + lonStr;
        }
        return null;
    }

    public DateTime tryParseInstant(String input) {
        input = input.trim();
        if (input.equalsIgnoreCase("now")) {
            return new DateTime();
        }
        Matcher matcher = USER_NOW_RELATIVE_FORMAT.matcher(input);
        if (matcher.matches()) {
            int delta = tryParseDuration(matcher.group(2));
            if (delta < 0) {
                return null;
            } else if (matcher.group(1).equals("-")) {
                return new DateTime().minusSeconds(delta);
            } else {
                return new DateTime().plusSeconds(delta);
            }
        }
        try {
            return userFullParser.parseDateTime(input);
        } catch (IllegalArgumentException e) {
        }
        //        if (!locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
        //            return null;
        //        }
        return null;
    }

    public Interval tryParseInterval(String input) {
        for (int i = 0; i < userPartialParsers.length; i++) {
            try {
                DateTime start = userPartialParsers[i].parseDateTime(input);
                DateTime end = start.plusSeconds(USER_INTERVAL_DURATIONS[i]);
                return new Interval(start, end);
            } catch (IllegalArgumentException e) {
            }
        }
        if (!locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
            return null;
        }
        DateTime start = new DateMidnight().toDateTime();
        boolean match = false;
        if (input.equalsIgnoreCase("today")) {
            match = true;
        } else if (input.equalsIgnoreCase("yesterday")) {
            start = start.minusDays(1);
            match = true;
        } else if (input.equalsIgnoreCase("tomorrow") || input.equalsIgnoreCase("tmw")) {
            start = start.plusDays(1);
            match = true;
        }
        if (match) {
            DateTime end = start.plusDays(1);
            return new Interval(start, end);
        }
        return null;
    }

    public int tryParseDuration(String input) {
        Matcher matcher = USER_DURATION_FORMAT.matcher(input);
        if (!matcher.matches()) {
            return -1;
        }
        int quant = Integer.parseInt(matcher.group(1));
        char unit = matcher.group(2).charAt(0);
        switch (unit) {
        case 's':
            return quant;
        case 'm':
            return quant * 60;
        case 'h':
            return quant * 3600;
        case 'd':
            return quant * 86400;
        default:
            return -1;
        }
    }

    public String formatDateTimeForDb(DateTime dt) {
        return TableConstants.nanoSecondsFromMillis(dt.getMillis());
    }

    public String formatIntervalForDb(Interval interval) {
        return formatDateTimeForDb(interval.getStart()) + "/" + formatDateTimeForDb(interval.getEnd());
    }

    public String formatNowForDb() {
        return formatDateTimeForDb(new DateTime());
    }

    public DateTime parseDateTimeFromDb(String dbString) {
        DateTime t = new DateTime(TableConstants.milliSecondsFromNanos(dbString), DateTimeZone.UTC);
        return t;
    }

    public Interval parseIntervalFromDb(String dbString) {
        // TODO: range should not be slash-separated but stored as two columns OR json in db...
        String[] split = dbString.split("/");
        return new Interval(parseDateTimeFromDb(split[0]), parseDateTimeFromDb(split[1]));
    }

    public String formatShortDateTimeForUser(DateTime dt) {
        return userShortFormatter.print(dt);
    }

    public String formatLongDateTimeForUser(DateTime dt) {
        return userLongFormatter.print(dt);
    }

    public String formatShortIntervalForUser(Interval interval) {
        return formatShortDateTimeForUser(interval.getStart()) + "-"
                + formatShortDateTimeForUser(interval.getEnd());
    }

    public String formatLongIntervalForUser(Interval interval) {
        return formatLongDateTimeForUser(interval.getStart()) + " - "
                + formatLongDateTimeForUser(interval.getEnd());
    }

    public double[] parseLocationFromDb(String dbString) {
        // TODO: geopoint should not be comma-separated but stored as four columns OR json in db...
        // TODO: note that this expects only 2 coordinates (x,y) ???
        String[] split = dbString.split(",");
        return new double[] { Double.parseDouble(split[0]), Double.parseDouble(split[1]) };
    }
}