/*********************************************************************************
* TotalCross Software Development Kit *
* Copyright (C) 1998, 1999 Wabasoft <www.wabasoft.com> *
* Copyright (C) 2000-2012 SuperWaba Ltda. *
* All Rights Reserved *
* *
* This library and virtual machine 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. *
* *
* This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 *
* A copy of this license is located in file license.txt at the root of this *
* SDK or can be downloaded here: *
* http://www.gnu.org/licenses/lgpl-3.0.txt *
* *
*********************************************************************************/
package totalcross.sys;
import totalcross.util.*;
/**
* Time identifies a date and time.
* <p>
* Here is an example of Time being used to display the current date:
*
* <pre>
* Time t = new Time();
* ...
* label.setText("Today is " + t.year + "/" + t.month + "/" + t.day);
* </pre>
* You can also call the update method to update the time with the current system values.
*/
public final class Time
{
/** The year as its full set of digits (year 2010 is 2010). */
public int year;
/** The month in the range of 1 to 12. */
public int month;
/** The day in the range of 1 to the last day in the month. */
public int day;
/** The hour in the range of 0 to 23. */
public int hour;
/** The minute in the range of 0 to 59. */
public int minute;
/** The second in the range of 0 to 59. */
public int second;
/** Milliseconds in the range of 0 to 999. */
public int millis;
public static final int SECONDS_PER_DAY = 24 * 3600;
/**
* Constructs a time object set to the current date and time.
*/
public Time()
{
update();
}
/** Updates the internal fields with the current timestamp.
* @since TotalCross 1.23
*/
public void update() // guich@tc123_65: renamed timeCreate to update and made it public
{
java.util.GregorianCalendar d = new java.util.GregorianCalendar();
year = d.get(java.util.GregorianCalendar.YEAR);
month = d.get(java.util.GregorianCalendar.MONTH) + 1;
day = d.get(java.util.GregorianCalendar.DATE);
hour = d.get(java.util.GregorianCalendar.HOUR_OF_DAY);
minute = d.get(java.util.GregorianCalendar.MINUTE);
second = d.get(java.util.GregorianCalendar.SECOND);
millis = d.get(java.util.GregorianCalendar.MILLISECOND);
}
public native void update4D();
/** Constructs a time object from a Date, zeroing the hour/minute/second and millis
* @since TotalCross 2.0
*/
public Time(Date d)
{
this(d.getDateInt(),0);
}
/** Constructs a time object from the given value.
* @see #getTimeLong
* @since SuperWaba 4.0
*/
public Time(long yyyymmddhhmmss)
{
second = (int)(yyyymmddhhmmss % 100); yyyymmddhhmmss /= 100;
minute = (int)(yyyymmddhhmmss % 100); yyyymmddhhmmss /= 100;
hour = (int)(yyyymmddhhmmss % 100); yyyymmddhhmmss /= 100;
day = (int)(yyyymmddhhmmss % 100); yyyymmddhhmmss /= 100;
month = (int)(yyyymmddhhmmss % 100); yyyymmddhhmmss /= 100;
year = (int)yyyymmddhhmmss;
}
/** Constructs a time object from the given date and time values.
* @since TotalCross 1.0 beta 4
*/
public Time(int yyyymmdd, int hhmmssmmm)
{
millis = hhmmssmmm % 1000; hhmmssmmm /= 1000;
second = (hhmmssmmm % 100); hhmmssmmm /= 100;
minute = (hhmmssmmm % 100); hhmmssmmm /= 100;
hour = (hhmmssmmm % 100);
day = (yyyymmdd % 100); yyyymmdd /= 100;
month = (yyyymmdd % 100); yyyymmdd /= 100;
year = yyyymmdd;
}
/** Creates a Time object with a String in the given Iso8601 format: <pre>YYYYMMDDTHH:MM:SS</pre>.
* @since SuperWaba 5.61
*/
public Time(String iso8601) throws InvalidNumberException // guich@561_4
{
char chars[] = iso8601.toCharArray();
year = Convert.toInt(new String(chars,0,4));
month = Convert.toInt(new String(chars, 4, 2));
day = Convert.toInt(new String(chars, 6, 2));
hour = Convert.toInt(new String(chars, 9, 2));
minute = Convert.toInt(new String(chars, 12, 2));
second = Convert.toInt(new String(chars, 15, 2));
}
/** Creates a time in the SQL' format.
* @param time Number of millis since 1/1/1970
* @param b Here only to differ from the other constructor that receives a long and its ignored.
* @throws InvalidDateException
* @since TotalCross 2.1
*/
public Time(long time, boolean b) throws InvalidDateException
{
millis = (int)(time % 1000); time /= 1000;
second = (int)(time % 60); time /= 60;
minute = (int)(time % 60); time /= 60;
hour = (int)(time % 24); time /= 24;
hour += Settings.timeZoneMinutes/60;
if (hour < 0) hour += 24;
Date d = new Date(Date.SQL_EPOCH.getDateInt());
d.advance((int)time);
day = d.getDay();
month = d.getMonth();
year = d.getYear();
}
/** Returns the number of millis since 1/1/1970
* @since TotalCross 2.1
*/
public long getTime() throws InvalidDateException
{
return new Date(this).getTime() + hour*60L*60L*1000L + (minute - Settings.timeZoneMinutes)*60L*1000L + second*1000L + millis;
}
/** Constructs a Time object, parsing the String and placing the fields depending on
* the flags that were set, using the Settings.timeSeparator as spliter.
* The number of parts must match the number of true flags, or an ArrayIndexOutOfBoundsException will be thrown.
*
* AM/PM is supported.
* @since TotalCross 1.3
*/
public Time(String time, boolean hasYear, boolean hasMonth, boolean hasDay, boolean hasHour, boolean hasMinute, boolean hasSeconds) throws InvalidNumberException
{
this(time, hasYear, hasMonth, hasDay, hasHour, hasMinute, hasSeconds, Settings.dateFormat);
}
/** Constructs a Time object, parsing the String and placing the fields depending on
* the flags that were set, using the Settings.timeSeparator as spliter.
* The number of parts must match the number of true flags, or an ArrayIndexOutOfBoundsException will be thrown.
* The dateFormat is only considered if hasYear, hasMonth and hasDay are all true.
*
* AM/PM is supported.
* @since TotalCross 2.1
*/
public Time(String time, boolean hasYear, boolean hasMonth, boolean hasDay, boolean hasHour, boolean hasMinute, boolean hasSeconds, byte dateFormat) throws InvalidNumberException
{
String timeLow = time.toLowerCase();
if (timeLow.endsWith("AM"))
time = time.substring(0,time.length()-2).trim();
else
if (timeLow.endsWith("PM"))
{
time = time.substring(0,time.length()-2).trim();
hour += 12;
}
try
{
StringBuffer seps = new StringBuffer(1); // guich@sqlite: accept any kind of separator
for (int i = 0, n = time.length(); i < n; i++)
{
char ch = time.charAt(i);
if (!('0' <= ch && ch <= '9'))
seps.append(ch);
}
String[] parts = Convert.tokenizeString(time, seps.toString().toCharArray());
int idx = 0;
if (hasYear && hasMonth && hasDay)
{
int p1 = Convert.toInt(parts[idx++]);
int p2 = Convert.toInt(parts[idx++]);
int p3 = Convert.toInt(parts[idx++]);
switch (dateFormat)
{
case Settings.DATE_DMY: day = p1; month = p2; year = p3; break;
case Settings.DATE_MDY: day = p2; month = p1; year = p3; break;
case Settings.DATE_YMD: day = p3; month = p2; year = p1; break;
}
}
else
{
if (hasYear && parts.length < idx) year = Convert.toInt(parts[idx++]);
if (hasMonth && parts.length < idx) month = Convert.toInt(parts[idx++]);
if (hasDay && parts.length < idx) day = Convert.toInt(parts[idx++]);
}
if (hasHour && parts.length < idx) hour += Convert.toInt(parts[idx++]);
if (hasMinute && parts.length < idx) minute = Convert.toInt(parts[idx++]);
if (hasSeconds && parts.length < idx) second = Convert.toInt(parts[idx++]);
}
catch (InvalidNumberException ine) {}
}
/** Returns the time in the format YYYYMMDDHHMMSS as a long value. It does
* not include the millis.
* @since SuperWaba 4.0
*/
public long getTimeLong()
{
int i = hour * 10000 + minute * 100 + second;
return year * 10000000000L + month * 100000000L + day * 1000000 + i;
}
/** Returns the time in the format YYYYMMDDHHmmSSmmm as a long value. It does
* include the millis.
* @since TotalCross 2.0
*/
public long getSQLLong()
{
return year * 10000000000000L + month * 100000000000L + day * 1000000000L + hour * 10000000L + minute * 100000L + second * 1000L + millis;
}
/** Returns this date in the format <code>YYYY-MM-DD HH:mm:SS.mmm</code>
* @since TotalCross 2.0
*/
public String getSQLString() // guich@tc115_22
{
StringBuffer sb = new StringBuffer(20);
return getSQLString(sb);
}
/** Returns this date in the format <code>YYYY-MM-DD HH:mm:SS.mmm</code>
* @since TotalCross 2.0
*/
public String getSQLString(StringBuffer sb) // guich@tc115_22
{
sb.append(year);
sb.append('-');
if (month < 10) sb.append('0'); sb.append(month);
sb.append('-');
if (day < 10) sb.append('0'); sb.append(day);
sb.append(' ');
return dump(sb, ":", true).toString();
}
/** Constructs a new time with the given values. The values are not checked.
* @since SuperWaba 3.5
*/
public Time(int year, int month, int day, int hour, int minute, int second, int millis)
{
this.year = year;
this.month = month;
this.day = day;
this.hour = hour;
this.minute = minute;
this.second = second;
this.millis = millis;
}
/** Returns the time in format specified in totalcross.sys.Settings (does NOT include the date neither the millis).
* To return the date, use class totalcross.util.Date. So, to get a String with the date and time, use:
* <pre>
* Time t = new Time();
* String dateAndTime = new Date(t) + " " + t;
* </pre>
*/
public String toString() // guich@300_56
{
return toString(Convert.toString(Settings.timeSeparator));
}
/** Returns the time in format specified in totalcross.sys.Settings (does NOT include the date neither the millis).
* To return the date, use class totalcross.util.Date. So, to get a String with the date and time, use:
* <pre>
* Time t = new Time();
* String dateAndTime = new Date(t) + " " + t;
* </pre>
* @since TotalCross 1.15
*/
public String toString(String timeSeparator) // guich@tc115_22
{
return dump(new StringBuffer(20), timeSeparator, false).toString();
}
/** Dumps the time into the given StringBuffer.
* @since TotalCross 1.24
*/
public StringBuffer dump(StringBuffer sb, String timeSeparator, boolean includeMillis) // guich@tc123_62
{
boolean useAmPm = !Settings.is24Hour;
if (useAmPm) // guich@566_40
{
if (hour == 0 || hour == 12)
sb.append("12");
else
{
int h = hour < 12 ? hour : (hour-12);
if (h < 10) sb.append('0');
sb.append(h);
}
}
else
{
if (hour < 10) sb.append('0');
sb.append(hour);
}
sb.append(timeSeparator);
if (minute < 10) sb.append('0');
sb.append(minute);
sb.append(timeSeparator);
if (second < 10) sb.append('0');
sb.append(second);
if (includeMillis)
{
sb.append(".");
if (millis < 10)
sb.append("00");
else
if (millis < 100)
sb.append("0");
sb.append(millis);
}
if (useAmPm)
sb.append(hour >= 12 ? " PM":" AM"); // guich@566_40: 12 is already pm
return sb;
}
public boolean equals(Object o)
{
if (o == this) // flsobral@tc100b4: If both are the same object, avoids wasting time comparing their fields.
return true;
if (o instanceof Time)
{
Time t = (Time) o;
return year == t.year && month == t.month && day == t.day &&
hour == t.hour && minute == t.minute && second == t.second && millis == t.millis;
}
return false;
}
/** Converts this time object to a string in the Iso8601 format: <pre>YYYYMMDDTHH:MM:SS</pre>.
* @since SuperWaba 5.61
*/
public String toIso8601() // guich@561_4
{
StringBuffer sb = new StringBuffer(20);
sb.setLength(0);
// flsobral@tc100b4: calculate first part, instead of creating a Date object to call getDateInt.
sb.append((year * 10000)+ (month*100) + day);
sb.append('T');
if (hour < 10) sb.append('0');
sb.append(hour);
sb.append(':');
if (minute < 10) sb.append('0');
sb.append(minute);
sb.append(':');
if (second < 10) sb.append('0');
sb.append(second);
return sb.toString();
}
/** Returns true if the time is valid. Note that the date part is NOT checked, only hour, minute, second and millis are checked against valid ranges.
* @since TotalCross 1.22
*/
public boolean isValid()
{
return 0 <= hour && hour <= 23 && 0 <= minute && minute <= 59 && 0 <= second && second <= 59 && 0 <= millis && millis <= 999;
}
/** Increments or decrements the fields below. Note that this method does NOT update the
* day/month/year fields.
* <br><br>Parameters can be positive (to increment), zero (to keep it), or negative (to decrement).
* @since TotalCross 1.24
*/
public void inc(int hours, int minutes, int seconds) // guich@tc124_5
{
// convert everyone to seconds
int is = seconds + minutes * 60 + hours * 3600;
int ts = this.second + this.minute * 60 + this.hour * 3600;
int s = ts + is;
if (s > SECONDS_PER_DAY) // above a single day?
s %= SECONDS_PER_DAY;
else
if (s < 0) // previous day?
s = SECONDS_PER_DAY - (-s % SECONDS_PER_DAY);
this.second = s % 60; s = s / 60;
this.minute = s % 60; s = s / 60;
this.hour = s;
}
}