/* * Copyright (C) 2009 JavaRosa * * 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.openrosa.client.jr.core.model.utils; import java.util.Date; import java.util.Vector; import org.openrosa.client.jr.core.services.locale.Localization; import org.openrosa.client.jr.core.util.MathUtils; /** * Static utility methods for Dates in j2me * * @author Clayton Sims * */ public class DateUtils { private static final int MONTH_OFFSET = 0; //(1 - Calendar.JANUARY); public static final int FORMAT_ISO8601 = 1; public static final int FORMAT_HUMAN_READABLE_SHORT = 2; public static final int FORMAT_HUMAN_READABLE_DAYS_FROM_TODAY = 5; //public static final int FORMAT_HUMAN_READABLE_LONG = 3; public static final int FORMAT_TIMESTAMP_SUFFIX = 7; public static final long DAY_IN_MS = 86400000l; public DateUtils() { super(); } public static class DateFields { public DateFields () { year = 1970; month = 1; day = 1; hour = 0; minute = 0; second = 0; secTicks = 0; // tzStr = "Z"; // tzOffset = 0; } public int year; public int month; //1-12 public int day; //1-31 public int hour; //0-23 public int minute; //0-59 public int second; //0-59 public int secTicks; //0-999 (ms) // public String tzStr; // public int tzOffset; //s ahead of UTC public boolean check () { return true; //(inRange(month, 1, 12) && inRange(day, 1, daysInMonth(month - MONTH_OFFSET, year)) && //inRange(hour, 0, 23) && inRange(minute, 0, 59) && inRange(second, 0, 59) && inRange(secTicks, 0, 999)); } } public static DateFields getFields (Date d) { //Calendar cd = Calendar.getInstance(); //cd.setTime(d); DateFields fields = new DateFields(); /*fields.year = cd.get(Calendar.YEAR); fields.month = cd.get(Calendar.MONTH) + MONTH_OFFSET; fields.day = cd.get(Calendar.DAY_OF_MONTH); fields.hour = cd.get(Calendar.HOUR_OF_DAY); fields.minute = cd.get(Calendar.MINUTE); fields.second = cd.get(Calendar.SECOND); fields.secTicks = cd.get(Calendar.MILLISECOND);*/ return fields; } public static Date getDate (DateFields f) { /*Calendar cd = Calendar.getInstance(); cd.set(Calendar.YEAR, f.year); cd.set(Calendar.MONTH, f.month - MONTH_OFFSET); cd.set(Calendar.DAY_OF_MONTH, f.day); cd.set(Calendar.HOUR_OF_DAY, f.hour); cd.set(Calendar.MINUTE, f.minute); cd.set(Calendar.SECOND, f.second); cd.set(Calendar.MILLISECOND, f.secTicks);*/ return new Date(); //cd.getTime(); } /* ==== FORMATTING DATES/TIMES TO STANDARD STRINGS ==== */ public static String formatDateTime (Date d, int format) { if (d == null) return ""; DateFields fields = getFields(d); String delim; switch (format) { case FORMAT_ISO8601: delim = "T"; break; case FORMAT_TIMESTAMP_SUFFIX: delim = ""; break; default: delim = " "; break; } return formatDate(fields, format) + delim + formatTime(fields, format); } public static String formatDate (Date d, int format) { return (d == null ? "" :formatDate(getFields(d), format)); } public static String formatTime (Date d, int format) { return (d == null ? "" : formatTime(getFields(d), format)); } private static String formatDate (DateFields f, int format) { switch (format) { case FORMAT_ISO8601: return formatDateISO8601(f); case FORMAT_HUMAN_READABLE_SHORT: return formatDateColloquial(f); case FORMAT_HUMAN_READABLE_DAYS_FROM_TODAY: return formatDaysFromToday(f); case FORMAT_TIMESTAMP_SUFFIX: return formatDateSuffix(f); default: return null; } } private static String formatTime (DateFields f, int format) { switch (format) { case FORMAT_ISO8601: return formatTimeISO8601(f); case FORMAT_HUMAN_READABLE_SHORT: return formatTimeColloquial(f); case FORMAT_TIMESTAMP_SUFFIX: return formatTimeSuffix(f); default: return null; } } private static String formatDateISO8601 (DateFields f) { return f.year + "-" + intPad(f.month, 2) + "-" + intPad(f.day, 2); } private static String formatDateColloquial (DateFields f) { return intPad(f.day, 2) + "/" + intPad(f.month, 2) + "/" + (new Integer(f.year)).toString().substring(2, 4); } private static String formatDateSuffix (DateFields f) { return f.year + intPad(f.month, 2) + intPad(f.day, 2); } private static String formatTimeISO8601 (DateFields f) { return intPad(f.hour, 2) + ":" + intPad(f.minute, 2) + ":" + intPad(f.second, 2) + "." + intPad(f.secTicks, 3); //want to add time zone info to be fully ISO-8601 compliant, but API is totally on crack! } private static String formatTimeColloquial (DateFields f) { return intPad(f.hour, 2) + ":" + intPad(f.minute, 2); } private static String formatTimeSuffix (DateFields f) { return intPad(f.hour, 2) + intPad(f.minute, 2) + intPad(f.second, 2); } /* ==== PARSING DATES/TIMES FROM STANDARD STRINGS ==== */ public static Date parseDateTime (String str) { DateFields fields = new DateFields(); int i = str.indexOf("T"); if (i != -1) { if (!parseDate(str.substring(0, i), fields) || !parseTime(str.substring(i + 1), fields)) { return null; } } else { if (!parseDate(str, fields)) { return null; } } return getDate(fields); } public static Date parseDate (String str) { DateFields fields = new DateFields(); if (!parseDate(str, fields)) { return null; } return getDate(fields); } public static Date parseTime (String str) { DateFields fields = new DateFields(); if (!parseTime(str, fields)) { return null; } return getDate(fields); } private static boolean parseDate (String dateStr, DateFields f) { Vector pieces = split(dateStr, "-", false); if (pieces.size() != 3) return false; try { f.year = Integer.parseInt((String)pieces.elementAt(0)); f.month = Integer.parseInt((String)pieces.elementAt(1)); f.day = Integer.parseInt((String)pieces.elementAt(2)); } catch (NumberFormatException nfe) { return false; } return f.check(); } private static boolean parseTime (String timeStr, DateFields f) { Vector pieces = split(timeStr, ":", false); if (pieces.size() != 2 && pieces.size() != 3) return false; try { f.hour = Integer.parseInt((String)pieces.elementAt(0)); f.minute = Integer.parseInt((String)pieces.elementAt(1)); if (pieces.size() == 3) { String secStr = (String)pieces.elementAt(2); int i; for (i = 0; i < secStr.length(); i++) { char c = secStr.charAt(i); if (!Character.isDigit(c) && c != '.') break; } secStr = secStr.substring(0, i); double fsec = Double.parseDouble(secStr); f.second = (int)fsec; f.secTicks = (int)(1000.0 * (fsec - f.second)); } } catch (NumberFormatException nfe) { return false; } return f.check(); } /* ==== DATE UTILITY FUNCTIONS ==== */ public static Date getDate (int year, int month, int day) { DateFields f = new DateFields(); f.year = year; f.month = month; f.day = day; return (f.check() ? getDate(f) : null); } /** * * @return new Date object with same date but time set to midnight (in current timezone) */ public static Date roundDate (Date d) { DateFields f = getFields(d); return getDate(f.year, f.month, f.day); } public static Date today () { return roundDate(new Date()); } /* ==== CALENDAR FUNCTIONS ==== */ /** * Returns the number of days in the month given for * a given year. * @param month The month to be tested * @param year The year in which the month is to be tested * @return the number of days in the given month on the given * year. */ /*public static int daysInMonth (int month, int year) { if (month == Calendar.APRIL || month == Calendar.JUNE || month == Calendar.SEPTEMBER || month == Calendar.NOVEMBER) { return 30; } else if (month == Calendar.FEBRUARY) { return 28 + (isLeap(year) ? 1 : 0); } else { return 31; } }*/ /** * Determines whether a year is a leap year in the * proleptic Gregorian calendar. * * @param year The year to be tested * @return True, if the year given is a leap year, * false otherwise. */ public static boolean isLeap (int year) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); } /* ==== Parsing to Human Text ==== */ /** * Provides text representing a span of time. * * @param f The fields for the date to be compared against the current date. * @return a string which is a human readable representation of the difference between * the provided date and the current date. */ private static String formatDaysFromToday(DateFields f) { String daysAgoStr = ""; Date d = DateUtils.getDate(f); int daysAgo = DateUtils.daysSinceEpoch(new Date()) - DateUtils.daysSinceEpoch(d); if (daysAgo == 0) { return Localization.get("date.today"); } else if (daysAgo == 1) { return Localization.get("date.yesterday"); } else if (daysAgo == 2) { return Localization.get("date.twoago", new String[] {String.valueOf(daysAgo)}); } else if (daysAgo > 2 && daysAgo <= 6) { return Localization.get("date.nago", new String[] {String.valueOf(daysAgo)}); } else if (daysAgo == -1) { return Localization.get("date.tomorrow"); } else if (daysAgo < -1 && daysAgo >= -6) { return Localization.get("date.nfromnow", new String[] {String.valueOf(-daysAgo)}); } else { return DateUtils.formatDate(f, DateUtils.FORMAT_HUMAN_READABLE_SHORT); } } /* ==== DATE OPERATIONS ==== */ /** * Creates a Date object representing the amount of time between the * reference date, and the given parameters. * @param ref The starting reference date * @param type "week", or "month", representing the time period which is to be returned. * @param start "sun", "mon", ... etc. representing the start of the time period. * @param beginning true=return first day of period, false=return last day of period * @param includeToday Whether to include the current date in the returned calculation * @param nAgo How many periods ago. 1=most recent period, 0=period in progress * @return a Date object representing the amount of time between the * reference date, and the given parameters. */ /*public static Date getPastPeriodDate (Date ref, String type, String start, boolean beginning, boolean includeToday, int nAgo) { Date d = null; if (type.equals("week")) { //1 week period //start: day of week that starts period //beginning: true=return first day of period, false=return last day of period //includeToday: whether today's date can count as the last day of the period //nAgo: how many periods ago; 1=most recent period, 0=period in progress int target_dow = -1, current_dow = -1, diff; int offset = (includeToday ? 1 : 0); if (start.equals("sun")) target_dow = 0; else if (start.equals("mon")) target_dow = 1; else if (start.equals("tue")) target_dow = 2; else if (start.equals("wed")) target_dow = 3; else if (start.equals("thu")) target_dow = 4; else if (start.equals("fri")) target_dow = 5; else if (start.equals("sat")) target_dow = 6; if (target_dow == -1) throw new RuntimeException(); Calendar cd = Calendar.getInstance(); cd.setTime(ref); switch(cd.get(Calendar.DAY_OF_WEEK)) { case Calendar.SUNDAY: current_dow = 0; break; case Calendar.MONDAY: current_dow = 1; break; case Calendar.TUESDAY: current_dow = 2; break; case Calendar.WEDNESDAY: current_dow = 3; break; case Calendar.THURSDAY: current_dow = 4; break; case Calendar.FRIDAY: current_dow = 5; break; case Calendar.SATURDAY: current_dow = 6; break; default: throw new RuntimeException(); //something is wrong } diff = (((current_dow - target_dow) + (7 + offset)) % 7 - offset) + (7 * nAgo) - (beginning ? 0 : 6); //booyah d = new Date(ref.getTime() - diff * DAY_IN_MS); } else if (type.equals("month")) { //not supported } else { throw new IllegalArgumentException(); } return d; }*/ /** * Gets the number of months separating the two dates. * @param earlierDate The earlier date, chronologically * @param laterDate The later date, chronologically * @return the number of months separating the two dates. */ /*public static int getMonthsDifference(Date earlierDate, Date laterDate) { Date span = new Date(laterDate.getTime() - earlierDate.getTime()); Date firstDate = new Date(0); Calendar calendar = Calendar.getInstance(); calendar.setTime(firstDate); int firstYear = calendar.get(Calendar.YEAR); int firstMonth = calendar.get(Calendar.MONTH); calendar.setTime(span); int spanYear = calendar.get(Calendar.YEAR); int spanMonth = calendar.get(Calendar.MONTH); int months = (spanYear - firstYear)*12 + (spanMonth - firstMonth); return months; }*/ /** * @param date the date object to be analyzed * @return The number of days (as a double precision floating point) since the Epoch */ public static int daysSinceEpoch(Date date) { return dateDiff(getDate(1970, 1, 1), date); } /** * add n days to date d * * @param d * @param n * @return */ public static Date dateAdd (Date d, int n) { return roundDate(new Date(roundDate(d).getTime() + DAY_IN_MS * n + DAY_IN_MS / 2)); //half-day offset is needed to handle differing DST offsets! } /** * return the number of days between a and b, positive if b is later than a * * @param a * @param b * @return # days difference */ public static int dateDiff (Date a, Date b) { return (int)MathUtils.divLongNotSuck(roundDate(b).getTime() - roundDate(a).getTime() + DAY_IN_MS / 2, DAY_IN_MS); //half-day offset is needed to handle differing DST offsets! } /* ==== UTILITY ==== */ /** * Tokenizes a string based on the given delimiter string * @param original The string to be split * @param delimiter The delimeter to be used * @return An array of strings contained in original which were * seperated by the delimeter */ public static Vector split (String str, String delimiter, boolean combineMultipleDelimiters) { Vector pieces = new Vector(); int index = str.indexOf(delimiter); while (index >= 0) { pieces.addElement(str.substring(0, index)); str = str.substring(index + delimiter.length()); index = str.indexOf(delimiter); } pieces.addElement(str); if (combineMultipleDelimiters) { for (int i = 0; i < pieces.size(); i++) { if (((String)pieces.elementAt(i)).length() == 0) { pieces.removeElementAt(i); i--; } } } return pieces; } /** * Converts an integer to a string, ensuring that the string * contains a certain number of digits * @param n The integer to be converted * @param pad The length of the string to be returned * @return A string representing n, which has pad - #digits(n) * 0's preceding the number. */ public static String intPad (int n, int pad) { String s = String.valueOf(n); while (s.length() < pad) s = "0" + s; return s; } private static boolean inRange(int x, int min, int max) { return (x >= min && x <= max); } /* ==== GARBAGE (backward compatibility; too lazy to remove them now) ==== */ public static String formatDateToTimeStamp(Date date) { return formatDateTime(date, FORMAT_ISO8601); } public static String getShortStringValue(Date val) { return formatDate(val, FORMAT_HUMAN_READABLE_SHORT); } public static String getXMLStringValue(Date val) { return formatDate(val, FORMAT_ISO8601); } public static String get24HourTimeFromDate(Date d) { return formatTime(d, FORMAT_HUMAN_READABLE_SHORT); } public static Date getDateFromString(String value) { return parseDate(value); } public static Date getDateTimeFromString(String value) { return parseDateTime(value); } public static boolean stringContains(String string,String substring){ if(string == null || substring == null){ return false; } if(string.indexOf(substring)== -1){ return false; }else{ return true; } } }