The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2006 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package android.text.format; |
| 18 | |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 19 | import android.util.TimeFormatException; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 20 | |
Neil Fuller | b8383a1 | 2018-11-16 21:46:33 +0000 | [diff] [blame] | 21 | import libcore.timezone.ZoneInfoDB; |
Aurimas Liutikas | 4037d51 | 2016-10-11 17:20:06 -0700 | [diff] [blame] | 22 | import libcore.util.ZoneInfo; |
Aurimas Liutikas | 4037d51 | 2016-10-11 17:20:06 -0700 | [diff] [blame] | 23 | |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 24 | import java.io.IOException; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 25 | import java.util.Locale; |
| 26 | import java.util.TimeZone; |
| 27 | |
| 28 | /** |
Scott Main | 31e0465 | 2011-09-28 19:14:40 -0700 | [diff] [blame] | 29 | * An alternative to the {@link java.util.Calendar} and |
| 30 | * {@link java.util.GregorianCalendar} classes. An instance of the Time class represents |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 31 | * a moment in time, specified with second precision. It is modelled after |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 32 | * struct tm. This class is not thread-safe and does not consider leap seconds. |
| 33 | * |
| 34 | * <p>This class has a number of issues and it is recommended that |
| 35 | * {@link java.util.GregorianCalendar} is used instead. |
| 36 | * |
| 37 | * <p>Known issues: |
| 38 | * <ul> |
| 39 | * <li>For historical reasons when performing time calculations all arithmetic currently takes |
| 40 | * place using 32-bit integers. This limits the reliable time range representable from 1902 |
| 41 | * until 2037.See the wikipedia article on the |
| 42 | * <a href="http://en.wikipedia.org/wiki/Year_2038_problem">Year 2038 problem</a> for details. |
| 43 | * Do not rely on this behavior; it may change in the future. |
| 44 | * </li> |
| 45 | * <li>Calling {@link #switchTimezone(String)} on a date that cannot exist, such as a wall time |
| 46 | * that was skipped due to a DST transition, will result in a date in 1969 (i.e. -1, or 1 second |
| 47 | * before 1st Jan 1970 UTC).</li> |
| 48 | * <li>Much of the formatting / parsing assumes ASCII text and is therefore not suitable for |
| 49 | * use with non-ASCII scripts.</li> |
Elliott Hughes | 5c955ee | 2015-03-27 15:18:12 -0700 | [diff] [blame] | 50 | * <li>No support for pseudo-zones like "GMT-07:00".</li> |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 51 | * </ul> |
Neil Fuller | bbf8871 | 2014-10-23 11:57:06 +0100 | [diff] [blame] | 52 | * |
| 53 | * @deprecated Use {@link java.util.GregorianCalendar} instead. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 54 | */ |
Neil Fuller | bbf8871 | 2014-10-23 11:57:06 +0100 | [diff] [blame] | 55 | @Deprecated |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 56 | public class Time { |
| 57 | private static final String Y_M_D_T_H_M_S_000 = "%Y-%m-%dT%H:%M:%S.000"; |
| 58 | private static final String Y_M_D_T_H_M_S_000_Z = "%Y-%m-%dT%H:%M:%S.000Z"; |
| 59 | private static final String Y_M_D = "%Y-%m-%d"; |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 60 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 61 | public static final String TIMEZONE_UTC = "UTC"; |
| 62 | |
| 63 | /** |
| 64 | * The Julian day of the epoch, that is, January 1, 1970 on the Gregorian |
| 65 | * calendar. |
| 66 | */ |
| 67 | public static final int EPOCH_JULIAN_DAY = 2440588; |
| 68 | |
| 69 | /** |
Svetoslav Ganov | 50f34d1 | 2010-12-03 16:05:40 -0800 | [diff] [blame] | 70 | * The Julian day of the Monday in the week of the epoch, December 29, 1969 |
| 71 | * on the Gregorian calendar. |
| 72 | */ |
| 73 | public static final int MONDAY_BEFORE_JULIAN_EPOCH = EPOCH_JULIAN_DAY - 3; |
| 74 | |
| 75 | /** |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 76 | * True if this is an allDay event. The hour, minute, second fields are |
| 77 | * all zero, and the date is displayed the same in all time zones. |
| 78 | */ |
| 79 | public boolean allDay; |
| 80 | |
| 81 | /** |
| 82 | * Seconds [0-61] (2 leap seconds allowed) |
| 83 | */ |
| 84 | public int second; |
| 85 | |
| 86 | /** |
| 87 | * Minute [0-59] |
| 88 | */ |
| 89 | public int minute; |
| 90 | |
| 91 | /** |
| 92 | * Hour of day [0-23] |
| 93 | */ |
| 94 | public int hour; |
| 95 | |
| 96 | /** |
| 97 | * Day of month [1-31] |
| 98 | */ |
| 99 | public int monthDay; |
| 100 | |
| 101 | /** |
| 102 | * Month [0-11] |
| 103 | */ |
| 104 | public int month; |
| 105 | |
| 106 | /** |
Scott Main | 31e0465 | 2011-09-28 19:14:40 -0700 | [diff] [blame] | 107 | * Year. For example, 1970. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 108 | */ |
| 109 | public int year; |
| 110 | |
| 111 | /** |
| 112 | * Day of week [0-6] |
| 113 | */ |
| 114 | public int weekDay; |
| 115 | |
| 116 | /** |
| 117 | * Day of year [0-365] |
| 118 | */ |
| 119 | public int yearDay; |
| 120 | |
| 121 | /** |
| 122 | * This time is in daylight savings time. One of: |
| 123 | * <ul> |
| 124 | * <li><b>positive</b> - in dst</li> |
| 125 | * <li><b>0</b> - not in dst</li> |
| 126 | * <li><b>negative</b> - unknown</li> |
| 127 | * </ul> |
| 128 | */ |
| 129 | public int isDst; |
| 130 | |
| 131 | /** |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 132 | * Offset in seconds from UTC including any DST offset. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 133 | */ |
| 134 | public long gmtoff; |
| 135 | |
| 136 | /** |
| 137 | * The timezone for this Time. Should not be null. |
| 138 | */ |
| 139 | public String timezone; |
| 140 | |
| 141 | /* |
| 142 | * Define symbolic constants for accessing the fields in this class. Used in |
| 143 | * getActualMaximum(). |
| 144 | */ |
| 145 | public static final int SECOND = 1; |
| 146 | public static final int MINUTE = 2; |
| 147 | public static final int HOUR = 3; |
| 148 | public static final int MONTH_DAY = 4; |
| 149 | public static final int MONTH = 5; |
| 150 | public static final int YEAR = 6; |
| 151 | public static final int WEEK_DAY = 7; |
| 152 | public static final int YEAR_DAY = 8; |
| 153 | public static final int WEEK_NUM = 9; |
| 154 | |
| 155 | public static final int SUNDAY = 0; |
| 156 | public static final int MONDAY = 1; |
| 157 | public static final int TUESDAY = 2; |
| 158 | public static final int WEDNESDAY = 3; |
| 159 | public static final int THURSDAY = 4; |
| 160 | public static final int FRIDAY = 5; |
| 161 | public static final int SATURDAY = 6; |
| 162 | |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 163 | // An object that is reused for date calculations. |
| 164 | private TimeCalculator calculator; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 165 | |
| 166 | /** |
| 167 | * Construct a Time object in the timezone named by the string |
| 168 | * argument "timezone". The time is initialized to Jan 1, 1970. |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 169 | * @param timezoneId string containing the timezone to use. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 170 | * @see TimeZone |
| 171 | */ |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 172 | public Time(String timezoneId) { |
| 173 | if (timezoneId == null) { |
| 174 | throw new NullPointerException("timezoneId is null!"); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 175 | } |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 176 | initialize(timezoneId); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 177 | } |
| 178 | |
| 179 | /** |
| 180 | * Construct a Time object in the default timezone. The time is initialized to |
| 181 | * Jan 1, 1970. |
| 182 | */ |
| 183 | public Time() { |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 184 | initialize(TimeZone.getDefault().getID()); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 185 | } |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 186 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 187 | /** |
| 188 | * A copy constructor. Construct a Time object by copying the given |
| 189 | * Time object. No normalization occurs. |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 190 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 191 | * @param other |
| 192 | */ |
| 193 | public Time(Time other) { |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 194 | initialize(other.timezone); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 195 | set(other); |
| 196 | } |
| 197 | |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 198 | /** Initialize the Time to 00:00:00 1/1/1970 in the specified timezone. */ |
| 199 | private void initialize(String timezoneId) { |
| 200 | this.timezone = timezoneId; |
| 201 | this.year = 1970; |
| 202 | this.monthDay = 1; |
| 203 | // Set the daylight-saving indicator to the unknown value -1 so that |
| 204 | // it will be recomputed. |
| 205 | this.isDst = -1; |
| 206 | |
| 207 | // A reusable object that performs the date/time calculations. |
| 208 | calculator = new TimeCalculator(timezoneId); |
| 209 | } |
| 210 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 211 | /** |
| 212 | * Ensures the values in each field are in range. For example if the |
| 213 | * current value of this calendar is March 32, normalize() will convert it |
| 214 | * to April 1. It also fills in weekDay, yearDay, isDst and gmtoff. |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 215 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 216 | * <p> |
| 217 | * If "ignoreDst" is true, then this method sets the "isDst" field to -1 |
| 218 | * (the "unknown" value) before normalizing. It then computes the |
Neil Fuller | 7079f20 | 2014-09-30 11:47:19 +0100 | [diff] [blame] | 219 | * time in milliseconds and sets the correct value for "isDst" if the |
| 220 | * fields resolve to a valid date / time. |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 221 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 222 | * <p> |
| 223 | * See {@link #toMillis(boolean)} for more information about when to |
Neil Fuller | 7079f20 | 2014-09-30 11:47:19 +0100 | [diff] [blame] | 224 | * use <tt>true</tt> or <tt>false</tt> for "ignoreDst" and when {@code -1} |
| 225 | * might be returned. |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 226 | * |
Neil Fuller | 7079f20 | 2014-09-30 11:47:19 +0100 | [diff] [blame] | 227 | * @return the UTC milliseconds since the epoch, or {@code -1} |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 228 | */ |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 229 | public long normalize(boolean ignoreDst) { |
| 230 | calculator.copyFieldsFromTime(this); |
| 231 | long timeInMillis = calculator.toMillis(ignoreDst); |
| 232 | calculator.copyFieldsToTime(this); |
| 233 | return timeInMillis; |
| 234 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 235 | |
| 236 | /** |
| 237 | * Convert this time object so the time represented remains the same, but is |
| 238 | * instead located in a different timezone. This method automatically calls |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 239 | * normalize() in some cases. |
| 240 | * |
| 241 | * <p>This method can return incorrect results if the date / time cannot be normalized. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 242 | */ |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 243 | public void switchTimezone(String timezone) { |
| 244 | calculator.copyFieldsFromTime(this); |
| 245 | calculator.switchTimeZone(timezone); |
| 246 | calculator.copyFieldsToTime(this); |
| 247 | this.timezone = timezone; |
| 248 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 249 | |
| 250 | private static final int[] DAYS_PER_MONTH = { 31, 28, 31, 30, 31, 30, 31, |
| 251 | 31, 30, 31, 30, 31 }; |
| 252 | |
| 253 | /** |
| 254 | * Return the maximum possible value for the given field given the value of |
| 255 | * the other fields. Requires that it be normalized for MONTH_DAY and |
| 256 | * YEAR_DAY. |
| 257 | * @param field one of the constants for HOUR, MINUTE, SECOND, etc. |
| 258 | * @return the maximum value for the field. |
| 259 | */ |
| 260 | public int getActualMaximum(int field) { |
| 261 | switch (field) { |
| 262 | case SECOND: |
| 263 | return 59; // leap seconds, bah humbug |
| 264 | case MINUTE: |
| 265 | return 59; |
| 266 | case HOUR: |
| 267 | return 23; |
| 268 | case MONTH_DAY: { |
| 269 | int n = DAYS_PER_MONTH[this.month]; |
| 270 | if (n != 28) { |
| 271 | return n; |
| 272 | } else { |
| 273 | int y = this.year; |
| 274 | return ((y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0)) ? 29 : 28; |
| 275 | } |
| 276 | } |
| 277 | case MONTH: |
| 278 | return 11; |
| 279 | case YEAR: |
| 280 | return 2037; |
| 281 | case WEEK_DAY: |
| 282 | return 6; |
| 283 | case YEAR_DAY: { |
| 284 | int y = this.year; |
| 285 | // Year days are numbered from 0, so the last one is usually 364. |
| 286 | return ((y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0)) ? 365 : 364; |
| 287 | } |
| 288 | case WEEK_NUM: |
| 289 | throw new RuntimeException("WEEK_NUM not implemented"); |
| 290 | default: |
| 291 | throw new RuntimeException("bad field=" + field); |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | /** |
| 296 | * Clears all values, setting the timezone to the given timezone. Sets isDst |
| 297 | * to a negative value to mean "unknown". |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 298 | * @param timezoneId the timezone to use. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 299 | */ |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 300 | public void clear(String timezoneId) { |
| 301 | if (timezoneId == null) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 302 | throw new NullPointerException("timezone is null!"); |
| 303 | } |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 304 | this.timezone = timezoneId; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 305 | this.allDay = false; |
| 306 | this.second = 0; |
| 307 | this.minute = 0; |
| 308 | this.hour = 0; |
| 309 | this.monthDay = 0; |
| 310 | this.month = 0; |
| 311 | this.year = 0; |
| 312 | this.weekDay = 0; |
| 313 | this.yearDay = 0; |
| 314 | this.gmtoff = 0; |
| 315 | this.isDst = -1; |
| 316 | } |
| 317 | |
| 318 | /** |
Kenny Root | 467cabe | 2011-07-25 15:58:31 -0700 | [diff] [blame] | 319 | * Compare two {@code Time} objects and return a negative number if {@code |
| 320 | * a} is less than {@code b}, a positive number if {@code a} is greater than |
| 321 | * {@code b}, or 0 if they are equal. |
| 322 | * |
Neil Fuller | 7079f20 | 2014-09-30 11:47:19 +0100 | [diff] [blame] | 323 | * <p> |
| 324 | * This method can return an incorrect answer when the date / time fields of |
| 325 | * either {@code Time} have been set to a local time that contradicts the |
| 326 | * available timezone information. |
| 327 | * |
Kenny Root | 467cabe | 2011-07-25 15:58:31 -0700 | [diff] [blame] | 328 | * @param a first {@code Time} instance to compare |
| 329 | * @param b second {@code Time} instance to compare |
| 330 | * @throws NullPointerException if either argument is {@code null} |
| 331 | * @throws IllegalArgumentException if {@link #allDay} is true but {@code |
| 332 | * hour}, {@code minute}, and {@code second} are not 0. |
| 333 | * @return a negative result if {@code a} is earlier, a positive result if |
Trevor Johns | 682c24e | 2016-04-12 10:13:47 -0700 | [diff] [blame] | 334 | * {@code b} is earlier, or 0 if they are equal. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 335 | */ |
Kenny Root | 467cabe | 2011-07-25 15:58:31 -0700 | [diff] [blame] | 336 | public static int compare(Time a, Time b) { |
| 337 | if (a == null) { |
| 338 | throw new NullPointerException("a == null"); |
| 339 | } else if (b == null) { |
| 340 | throw new NullPointerException("b == null"); |
| 341 | } |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 342 | a.calculator.copyFieldsFromTime(a); |
| 343 | b.calculator.copyFieldsFromTime(b); |
Kenny Root | 467cabe | 2011-07-25 15:58:31 -0700 | [diff] [blame] | 344 | |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 345 | return TimeCalculator.compare(a.calculator, b.calculator); |
Kenny Root | 467cabe | 2011-07-25 15:58:31 -0700 | [diff] [blame] | 346 | } |
| 347 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 348 | /** |
| 349 | * Print the current value given the format string provided. See man |
| 350 | * strftime for what means what. The final string must be less than 256 |
| 351 | * characters. |
| 352 | * @param format a string containing the desired format. |
| 353 | * @return a String containing the current time expressed in the current locale. |
| 354 | */ |
| 355 | public String format(String format) { |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 356 | calculator.copyFieldsFromTime(this); |
| 357 | return calculator.format(format); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 358 | } |
| 359 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 360 | /** |
Neil Fuller | 3d3d7f1 | 2018-01-17 21:13:21 +0000 | [diff] [blame] | 361 | * Return the current time in YYYYMMDDTHHMMSS<tz> format |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 362 | */ |
| 363 | @Override |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 364 | public String toString() { |
| 365 | // toString() uses its own TimeCalculator rather than the shared one. Otherwise crazy stuff |
| 366 | // happens during debugging when the debugger calls toString(). |
| 367 | TimeCalculator calculator = new TimeCalculator(this.timezone); |
| 368 | calculator.copyFieldsFromTime(this); |
| 369 | return calculator.toStringInternal(); |
| 370 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 371 | |
| 372 | /** |
| 373 | * Parses a date-time string in either the RFC 2445 format or an abbreviated |
| 374 | * format that does not include the "time" field. For example, all of the |
| 375 | * following strings are valid: |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 376 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 377 | * <ul> |
| 378 | * <li>"20081013T160000Z"</li> |
| 379 | * <li>"20081013T160000"</li> |
| 380 | * <li>"20081013"</li> |
| 381 | * </ul> |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 382 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 383 | * Returns whether or not the time is in UTC (ends with Z). If the string |
| 384 | * ends with "Z" then the timezone is set to UTC. If the date-time string |
| 385 | * included only a date and no time field, then the <code>allDay</code> |
| 386 | * field of this Time class is set to true and the <code>hour</code>, |
| 387 | * <code>minute</code>, and <code>second</code> fields are set to zero; |
| 388 | * otherwise (a time field was included in the date-time string) |
| 389 | * <code>allDay</code> is set to false. The fields <code>weekDay</code>, |
| 390 | * <code>yearDay</code>, and <code>gmtoff</code> are always set to zero, |
| 391 | * and the field <code>isDst</code> is set to -1 (unknown). To set those |
| 392 | * fields, call {@link #normalize(boolean)} after parsing. |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 393 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 394 | * To parse a date-time string and convert it to UTC milliseconds, do |
| 395 | * something like this: |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 396 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 397 | * <pre> |
| 398 | * Time time = new Time(); |
| 399 | * String date = "20081013T160000Z"; |
| 400 | * time.parse(date); |
| 401 | * long millis = time.normalize(false); |
| 402 | * </pre> |
| 403 | * |
| 404 | * @param s the string to parse |
| 405 | * @return true if the resulting time value is in UTC time |
| 406 | * @throws android.util.TimeFormatException if s cannot be parsed. |
| 407 | */ |
| 408 | public boolean parse(String s) { |
Elliott Hughes | 5ef4942 | 2012-07-25 18:47:15 -0700 | [diff] [blame] | 409 | if (s == null) { |
| 410 | throw new NullPointerException("time string is null"); |
| 411 | } |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 412 | if (parseInternal(s)) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 413 | timezone = TIMEZONE_UTC; |
| 414 | return true; |
| 415 | } |
| 416 | return false; |
| 417 | } |
| 418 | |
| 419 | /** |
| 420 | * Parse a time in the current zone in YYYYMMDDTHHMMSS format. |
| 421 | */ |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 422 | private boolean parseInternal(String s) { |
| 423 | int len = s.length(); |
| 424 | if (len < 8) { |
| 425 | throw new TimeFormatException("String is too short: \"" + s + |
| 426 | "\" Expected at least 8 characters."); |
| 427 | } |
| 428 | |
| 429 | boolean inUtc = false; |
| 430 | |
| 431 | // year |
| 432 | int n = getChar(s, 0, 1000); |
| 433 | n += getChar(s, 1, 100); |
| 434 | n += getChar(s, 2, 10); |
| 435 | n += getChar(s, 3, 1); |
| 436 | year = n; |
| 437 | |
| 438 | // month |
| 439 | n = getChar(s, 4, 10); |
| 440 | n += getChar(s, 5, 1); |
| 441 | n--; |
| 442 | month = n; |
| 443 | |
| 444 | // day of month |
| 445 | n = getChar(s, 6, 10); |
| 446 | n += getChar(s, 7, 1); |
| 447 | monthDay = n; |
| 448 | |
| 449 | if (len > 8) { |
| 450 | if (len < 15) { |
| 451 | throw new TimeFormatException( |
| 452 | "String is too short: \"" + s |
| 453 | + "\" If there are more than 8 characters there must be at least" |
| 454 | + " 15."); |
| 455 | } |
| 456 | checkChar(s, 8, 'T'); |
| 457 | allDay = false; |
| 458 | |
| 459 | // hour |
| 460 | n = getChar(s, 9, 10); |
| 461 | n += getChar(s, 10, 1); |
| 462 | hour = n; |
| 463 | |
| 464 | // min |
| 465 | n = getChar(s, 11, 10); |
| 466 | n += getChar(s, 12, 1); |
| 467 | minute = n; |
| 468 | |
| 469 | // sec |
| 470 | n = getChar(s, 13, 10); |
| 471 | n += getChar(s, 14, 1); |
| 472 | second = n; |
| 473 | |
| 474 | if (len > 15) { |
| 475 | // Z |
| 476 | checkChar(s, 15, 'Z'); |
| 477 | inUtc = true; |
| 478 | } |
| 479 | } else { |
| 480 | allDay = true; |
| 481 | hour = 0; |
| 482 | minute = 0; |
| 483 | second = 0; |
| 484 | } |
| 485 | |
| 486 | weekDay = 0; |
| 487 | yearDay = 0; |
| 488 | isDst = -1; |
| 489 | gmtoff = 0; |
| 490 | return inUtc; |
| 491 | } |
| 492 | |
| 493 | private void checkChar(String s, int spos, char expected) { |
| 494 | char c = s.charAt(spos); |
| 495 | if (c != expected) { |
| 496 | throw new TimeFormatException(String.format( |
| 497 | "Unexpected character 0x%02d at pos=%d. Expected 0x%02d (\'%c\').", |
| 498 | (int) c, spos, (int) expected, expected)); |
| 499 | } |
| 500 | } |
| 501 | |
| 502 | private static int getChar(String s, int spos, int mul) { |
| 503 | char c = s.charAt(spos); |
| 504 | if (Character.isDigit(c)) { |
| 505 | return Character.getNumericValue(c) * mul; |
| 506 | } else { |
| 507 | throw new TimeFormatException("Parse error at pos=" + spos); |
| 508 | } |
| 509 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 510 | |
| 511 | /** |
| 512 | * Parse a time in RFC 3339 format. This method also parses simple dates |
| 513 | * (that is, strings that contain no time or time offset). For example, |
| 514 | * all of the following strings are valid: |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 515 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 516 | * <ul> |
| 517 | * <li>"2008-10-13T16:00:00.000Z"</li> |
| 518 | * <li>"2008-10-13T16:00:00.000+07:00"</li> |
| 519 | * <li>"2008-10-13T16:00:00.000-07:00"</li> |
| 520 | * <li>"2008-10-13"</li> |
| 521 | * </ul> |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 522 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 523 | * <p> |
| 524 | * If the string contains a time and time offset, then the time offset will |
| 525 | * be used to convert the time value to UTC. |
| 526 | * </p> |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 527 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 528 | * <p> |
| 529 | * If the given string contains just a date (with no time field), then |
| 530 | * the {@link #allDay} field is set to true and the {@link #hour}, |
| 531 | * {@link #minute}, and {@link #second} fields are set to zero. |
| 532 | * </p> |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 533 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 534 | * <p> |
| 535 | * Returns true if the resulting time value is in UTC time. |
| 536 | * </p> |
| 537 | * |
| 538 | * @param s the string to parse |
| 539 | * @return true if the resulting time value is in UTC time |
Ken Shirriff | 670bf11 | 2009-04-27 09:50:41 -0700 | [diff] [blame] | 540 | * @throws android.util.TimeFormatException if s cannot be parsed. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 541 | */ |
| 542 | public boolean parse3339(String s) { |
Alon Albert | 11afa8a | 2012-01-26 14:25:19 -0800 | [diff] [blame] | 543 | if (s == null) { |
| 544 | throw new NullPointerException("time string is null"); |
| 545 | } |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 546 | if (parse3339Internal(s)) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 547 | timezone = TIMEZONE_UTC; |
| 548 | return true; |
| 549 | } |
| 550 | return false; |
| 551 | } |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 552 | |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 553 | private boolean parse3339Internal(String s) { |
| 554 | int len = s.length(); |
| 555 | if (len < 10) { |
| 556 | throw new TimeFormatException("String too short --- expected at least 10 characters."); |
| 557 | } |
| 558 | boolean inUtc = false; |
| 559 | |
| 560 | // year |
| 561 | int n = getChar(s, 0, 1000); |
| 562 | n += getChar(s, 1, 100); |
| 563 | n += getChar(s, 2, 10); |
| 564 | n += getChar(s, 3, 1); |
| 565 | year = n; |
| 566 | |
| 567 | checkChar(s, 4, '-'); |
| 568 | |
| 569 | // month |
| 570 | n = getChar(s, 5, 10); |
| 571 | n += getChar(s, 6, 1); |
| 572 | --n; |
| 573 | month = n; |
| 574 | |
| 575 | checkChar(s, 7, '-'); |
| 576 | |
| 577 | // day |
| 578 | n = getChar(s, 8, 10); |
| 579 | n += getChar(s, 9, 1); |
| 580 | monthDay = n; |
| 581 | |
| 582 | if (len >= 19) { |
| 583 | // T |
| 584 | checkChar(s, 10, 'T'); |
| 585 | allDay = false; |
| 586 | |
| 587 | // hour |
| 588 | n = getChar(s, 11, 10); |
| 589 | n += getChar(s, 12, 1); |
| 590 | |
| 591 | // Note that this.hour is not set here. It is set later. |
| 592 | int hour = n; |
| 593 | |
| 594 | checkChar(s, 13, ':'); |
| 595 | |
| 596 | // minute |
| 597 | n = getChar(s, 14, 10); |
| 598 | n += getChar(s, 15, 1); |
| 599 | // Note that this.minute is not set here. It is set later. |
| 600 | int minute = n; |
| 601 | |
| 602 | checkChar(s, 16, ':'); |
| 603 | |
| 604 | // second |
| 605 | n = getChar(s, 17, 10); |
| 606 | n += getChar(s, 18, 1); |
| 607 | second = n; |
| 608 | |
| 609 | // skip the '.XYZ' -- we don't care about subsecond precision. |
| 610 | |
| 611 | int tzIndex = 19; |
| 612 | if (tzIndex < len && s.charAt(tzIndex) == '.') { |
| 613 | do { |
| 614 | tzIndex++; |
| 615 | } while (tzIndex < len && Character.isDigit(s.charAt(tzIndex))); |
| 616 | } |
| 617 | |
| 618 | int offset = 0; |
| 619 | if (len > tzIndex) { |
| 620 | char c = s.charAt(tzIndex); |
| 621 | // NOTE: the offset is meant to be subtracted to get from local time |
| 622 | // to UTC. we therefore use 1 for '-' and -1 for '+'. |
| 623 | switch (c) { |
| 624 | case 'Z': |
| 625 | // Zulu time -- UTC |
| 626 | offset = 0; |
| 627 | break; |
| 628 | case '-': |
| 629 | offset = 1; |
| 630 | break; |
| 631 | case '+': |
| 632 | offset = -1; |
| 633 | break; |
| 634 | default: |
| 635 | throw new TimeFormatException(String.format( |
| 636 | "Unexpected character 0x%02d at position %d. Expected + or -", |
| 637 | (int) c, tzIndex)); |
| 638 | } |
| 639 | inUtc = true; |
| 640 | |
| 641 | if (offset != 0) { |
| 642 | if (len < tzIndex + 6) { |
| 643 | throw new TimeFormatException( |
| 644 | String.format("Unexpected length; should be %d characters", |
| 645 | tzIndex + 6)); |
| 646 | } |
| 647 | |
| 648 | // hour |
| 649 | n = getChar(s, tzIndex + 1, 10); |
| 650 | n += getChar(s, tzIndex + 2, 1); |
| 651 | n *= offset; |
| 652 | hour += n; |
| 653 | |
| 654 | // minute |
| 655 | n = getChar(s, tzIndex + 4, 10); |
| 656 | n += getChar(s, tzIndex + 5, 1); |
| 657 | n *= offset; |
| 658 | minute += n; |
| 659 | } |
| 660 | } |
| 661 | this.hour = hour; |
| 662 | this.minute = minute; |
| 663 | |
| 664 | if (offset != 0) { |
| 665 | normalize(false); |
| 666 | } |
| 667 | } else { |
| 668 | allDay = true; |
| 669 | this.hour = 0; |
| 670 | this.minute = 0; |
| 671 | this.second = 0; |
| 672 | } |
| 673 | |
| 674 | this.weekDay = 0; |
| 675 | this.yearDay = 0; |
| 676 | this.isDst = -1; |
| 677 | this.gmtoff = 0; |
| 678 | return inUtc; |
| 679 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 680 | |
| 681 | /** |
| 682 | * Returns the timezone string that is currently set for the device. |
| 683 | */ |
| 684 | public static String getCurrentTimezone() { |
| 685 | return TimeZone.getDefault().getID(); |
| 686 | } |
| 687 | |
| 688 | /** |
| 689 | * Sets the time of the given Time object to the current time. |
| 690 | */ |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 691 | public void setToNow() { |
| 692 | set(System.currentTimeMillis()); |
| 693 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 694 | |
| 695 | /** |
| 696 | * Converts this time to milliseconds. Suitable for interacting with the |
| 697 | * standard java libraries. The time is in UTC milliseconds since the epoch. |
| 698 | * This does an implicit normalization to compute the milliseconds but does |
| 699 | * <em>not</em> change any of the fields in this Time object. If you want |
| 700 | * to normalize the fields in this Time object and also get the milliseconds |
| 701 | * then use {@link #normalize(boolean)}. |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 702 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 703 | * <p> |
| 704 | * If "ignoreDst" is false, then this method uses the current setting of the |
| 705 | * "isDst" field and will adjust the returned time if the "isDst" field is |
| 706 | * wrong for the given time. See the sample code below for an example of |
| 707 | * this. |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 708 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 709 | * <p> |
| 710 | * If "ignoreDst" is true, then this method ignores the current setting of |
| 711 | * the "isDst" field in this Time object and will instead figure out the |
| 712 | * correct value of "isDst" (as best it can) from the fields in this |
| 713 | * Time object. The only case where this method cannot figure out the |
| 714 | * correct value of the "isDst" field is when the time is inherently |
| 715 | * ambiguous because it falls in the hour that is repeated when switching |
| 716 | * from Daylight-Saving Time to Standard Time. |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 717 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 718 | * <p> |
| 719 | * Here is an example where <tt>toMillis(true)</tt> adjusts the time, |
| 720 | * assuming that DST changes at 2am on Sunday, Nov 4, 2007. |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 721 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 722 | * <pre> |
| 723 | * Time time = new Time(); |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 724 | * time.set(4, 10, 2007); // set the date to Nov 4, 2007, 12am |
Scott Main | 688342f | 2013-12-17 13:47:55 -0800 | [diff] [blame] | 725 | * time.normalize(false); // this sets isDst = 1 |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 726 | * time.monthDay += 1; // changes the date to Nov 5, 2007, 12am |
| 727 | * millis = time.toMillis(false); // millis is Nov 4, 2007, 11pm |
| 728 | * millis = time.toMillis(true); // millis is Nov 5, 2007, 12am |
| 729 | * </pre> |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 730 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 731 | * <p> |
| 732 | * To avoid this problem, use <tt>toMillis(true)</tt> |
| 733 | * after adding or subtracting days or explicitly setting the "monthDay" |
| 734 | * field. On the other hand, if you are adding |
| 735 | * or subtracting hours or minutes, then you should use |
| 736 | * <tt>toMillis(false)</tt>. |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 737 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 738 | * <p> |
| 739 | * You should also use <tt>toMillis(false)</tt> if you want |
| 740 | * to read back the same milliseconds that you set with {@link #set(long)} |
Neil Fuller | 3d3d7f1 | 2018-01-17 21:13:21 +0000 | [diff] [blame] | 741 | * or {@link #set(Time)} or after parsing a date string. |
Neil Fuller | 7079f20 | 2014-09-30 11:47:19 +0100 | [diff] [blame] | 742 | * |
| 743 | * <p> |
| 744 | * This method can return {@code -1} when the date / time fields have been |
| 745 | * set to a local time that conflicts with available timezone information. |
| 746 | * For example, when daylight savings transitions cause an hour to be |
| 747 | * skipped: times within that hour will return {@code -1} if isDst = |
| 748 | * {@code -1}. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 749 | */ |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 750 | public long toMillis(boolean ignoreDst) { |
| 751 | calculator.copyFieldsFromTime(this); |
| 752 | return calculator.toMillis(ignoreDst); |
| 753 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 754 | |
| 755 | /** |
| 756 | * Sets the fields in this Time object given the UTC milliseconds. After |
| 757 | * this method returns, all the fields are normalized. |
| 758 | * This also sets the "isDst" field to the correct value. |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 759 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 760 | * @param millis the time in UTC milliseconds since the epoch. |
| 761 | */ |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 762 | public void set(long millis) { |
| 763 | allDay = false; |
| 764 | calculator.timezone = timezone; |
| 765 | calculator.setTimeInMillis(millis); |
| 766 | calculator.copyFieldsToTime(this); |
| 767 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 768 | |
| 769 | /** |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 770 | * Format according to RFC 2445 DATE-TIME type. |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 771 | * |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 772 | * <p>The same as format("%Y%m%dT%H%M%S"), or format("%Y%m%dT%H%M%SZ") for a Time with a |
| 773 | * timezone set to "UTC". |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 774 | */ |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 775 | public String format2445() { |
| 776 | calculator.copyFieldsFromTime(this); |
| 777 | return calculator.format2445(!allDay); |
| 778 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 779 | |
| 780 | /** |
| 781 | * Copy the value of that to this Time object. No normalization happens. |
| 782 | */ |
| 783 | public void set(Time that) { |
| 784 | this.timezone = that.timezone; |
| 785 | this.allDay = that.allDay; |
| 786 | this.second = that.second; |
| 787 | this.minute = that.minute; |
| 788 | this.hour = that.hour; |
| 789 | this.monthDay = that.monthDay; |
| 790 | this.month = that.month; |
| 791 | this.year = that.year; |
| 792 | this.weekDay = that.weekDay; |
| 793 | this.yearDay = that.yearDay; |
| 794 | this.isDst = that.isDst; |
| 795 | this.gmtoff = that.gmtoff; |
| 796 | } |
| 797 | |
| 798 | /** |
| 799 | * Sets the fields. Sets weekDay, yearDay and gmtoff to 0, and isDst to -1. |
| 800 | * Call {@link #normalize(boolean)} if you need those. |
| 801 | */ |
| 802 | public void set(int second, int minute, int hour, int monthDay, int month, int year) { |
| 803 | this.allDay = false; |
| 804 | this.second = second; |
| 805 | this.minute = minute; |
| 806 | this.hour = hour; |
| 807 | this.monthDay = monthDay; |
| 808 | this.month = month; |
| 809 | this.year = year; |
| 810 | this.weekDay = 0; |
| 811 | this.yearDay = 0; |
| 812 | this.isDst = -1; |
| 813 | this.gmtoff = 0; |
| 814 | } |
| 815 | |
| 816 | /** |
| 817 | * Sets the date from the given fields. Also sets allDay to true. |
| 818 | * Sets weekDay, yearDay and gmtoff to 0, and isDst to -1. |
| 819 | * Call {@link #normalize(boolean)} if you need those. |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 820 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 821 | * @param monthDay the day of the month (in the range [1,31]) |
| 822 | * @param month the zero-based month number (in the range [0,11]) |
| 823 | * @param year the year |
| 824 | */ |
| 825 | public void set(int monthDay, int month, int year) { |
| 826 | this.allDay = true; |
| 827 | this.second = 0; |
| 828 | this.minute = 0; |
| 829 | this.hour = 0; |
| 830 | this.monthDay = monthDay; |
| 831 | this.month = month; |
| 832 | this.year = year; |
| 833 | this.weekDay = 0; |
| 834 | this.yearDay = 0; |
| 835 | this.isDst = -1; |
| 836 | this.gmtoff = 0; |
| 837 | } |
| 838 | |
| 839 | /** |
| 840 | * Returns true if the time represented by this Time object occurs before |
| 841 | * the given time. |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 842 | * |
Neil Fuller | 7079f20 | 2014-09-30 11:47:19 +0100 | [diff] [blame] | 843 | * <p> |
Neil Fuller | 71fbb81 | 2015-11-30 09:51:33 +0000 | [diff] [blame] | 844 | * Equivalent to {@code Time.compare(this, that) < 0}. See |
Neil Fuller | 7079f20 | 2014-09-30 11:47:19 +0100 | [diff] [blame] | 845 | * {@link #compare(Time, Time)} for details. |
| 846 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 847 | * @param that a given Time object to compare against |
| 848 | * @return true if this time is less than the given time |
| 849 | */ |
| 850 | public boolean before(Time that) { |
| 851 | return Time.compare(this, that) < 0; |
| 852 | } |
| 853 | |
| 854 | |
| 855 | /** |
| 856 | * Returns true if the time represented by this Time object occurs after |
| 857 | * the given time. |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 858 | * |
Neil Fuller | 7079f20 | 2014-09-30 11:47:19 +0100 | [diff] [blame] | 859 | * <p> |
Neil Fuller | 71fbb81 | 2015-11-30 09:51:33 +0000 | [diff] [blame] | 860 | * Equivalent to {@code Time.compare(this, that) > 0}. See |
Neil Fuller | 7079f20 | 2014-09-30 11:47:19 +0100 | [diff] [blame] | 861 | * {@link #compare(Time, Time)} for details. |
| 862 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 863 | * @param that a given Time object to compare against |
| 864 | * @return true if this time is greater than the given time |
| 865 | */ |
| 866 | public boolean after(Time that) { |
| 867 | return Time.compare(this, that) > 0; |
| 868 | } |
| 869 | |
| 870 | /** |
| 871 | * This array is indexed by the weekDay field (SUNDAY=0, MONDAY=1, etc.) |
| 872 | * and gives a number that can be added to the yearDay to give the |
| 873 | * closest Thursday yearDay. |
| 874 | */ |
| 875 | private static final int[] sThursdayOffset = { -3, 3, 2, 1, 0, -1, -2 }; |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 876 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 877 | /** |
| 878 | * Computes the week number according to ISO 8601. The current Time |
| 879 | * object must already be normalized because this method uses the |
| 880 | * yearDay and weekDay fields. |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 881 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 882 | * <p> |
| 883 | * In IS0 8601, weeks start on Monday. |
| 884 | * The first week of the year (week 1) is defined by ISO 8601 as the |
| 885 | * first week with four or more of its days in the starting year. |
| 886 | * Or equivalently, the week containing January 4. Or equivalently, |
| 887 | * the week with the year's first Thursday in it. |
| 888 | * </p> |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 889 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 890 | * <p> |
| 891 | * The week number can be calculated by counting Thursdays. Week N |
| 892 | * contains the Nth Thursday of the year. |
| 893 | * </p> |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 894 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 895 | * @return the ISO week number. |
| 896 | */ |
| 897 | public int getWeekNumber() { |
| 898 | // Get the year day for the closest Thursday |
| 899 | int closestThursday = yearDay + sThursdayOffset[weekDay]; |
| 900 | |
| 901 | // Year days start at 0 |
| 902 | if (closestThursday >= 0 && closestThursday <= 364) { |
| 903 | return closestThursday / 7 + 1; |
| 904 | } |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 905 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 906 | // The week crosses a year boundary. |
| 907 | Time temp = new Time(this); |
| 908 | temp.monthDay += sThursdayOffset[weekDay]; |
| 909 | temp.normalize(true /* ignore isDst */); |
| 910 | return temp.yearDay / 7 + 1; |
| 911 | } |
| 912 | |
| 913 | /** |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 914 | * Return a string in the RFC 3339 format. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 915 | * <p> |
| 916 | * If allDay is true, expresses the time as Y-M-D</p> |
| 917 | * <p> |
| 918 | * Otherwise, if the timezone is UTC, expresses the time as Y-M-D-T-H-M-S UTC</p> |
| 919 | * <p> |
| 920 | * Otherwise the time is expressed the time as Y-M-D-T-H-M-S +- GMT</p> |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 921 | * @return string in the RFC 3339 format. |
| 922 | */ |
| 923 | public String format3339(boolean allDay) { |
| 924 | if (allDay) { |
| 925 | return format(Y_M_D); |
| 926 | } else if (TIMEZONE_UTC.equals(timezone)) { |
| 927 | return format(Y_M_D_T_H_M_S_000_Z); |
| 928 | } else { |
| 929 | String base = format(Y_M_D_T_H_M_S_000); |
| 930 | String sign = (gmtoff < 0) ? "-" : "+"; |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 931 | int offset = (int) Math.abs(gmtoff); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 932 | int minutes = (offset % 3600) / 60; |
| 933 | int hours = offset / 3600; |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 934 | |
Roozbeh Pournader | 315a7c0 | 2012-09-17 20:34:53 -0700 | [diff] [blame] | 935 | return String.format(Locale.US, "%s%s%02d:%02d", base, sign, hours, minutes); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 936 | } |
| 937 | } |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 938 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 939 | /** |
Neil Fuller | 5898cc1 | 2019-08-20 18:23:16 +0100 | [diff] [blame] | 940 | * Returns true if the instant of the supplied time would be for the |
| 941 | * Gregorian calendar date January 1, 1970 <em>for a user observing UTC |
| 942 | * </em>, i.e. the timezone of the time object is ignored. |
| 943 | * <p> |
| 944 | * See {@link #getJulianDay(long, long)} for how to determine the Julian day |
| 945 | * for the timezone of the time object. |
Neil Fuller | 7079f20 | 2014-09-30 11:47:19 +0100 | [diff] [blame] | 946 | * <p> |
| 947 | * This method can return an incorrect answer when the date / time fields have |
| 948 | * been set to a local time that contradicts the available timezone information. |
| 949 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 950 | * @param time the time to test |
| 951 | * @return true if epoch. |
| 952 | */ |
| 953 | public static boolean isEpoch(Time time) { |
| 954 | long millis = time.toMillis(true); |
Neil Fuller | 5898cc1 | 2019-08-20 18:23:16 +0100 | [diff] [blame] | 955 | return getJulianDay(millis, 0 /* UTC offset */) == EPOCH_JULIAN_DAY; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 956 | } |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 957 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 958 | /** |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 959 | * Computes the Julian day number for a point in time in a particular |
Neil Fuller | 5898cc1 | 2019-08-20 18:23:16 +0100 | [diff] [blame] | 960 | * timezone. The Julian day for a given calendar date is the same for |
| 961 | * every timezone. For example, the Julian day for July 1, 2008 is |
| 962 | * 2454649. |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 963 | * |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 964 | * <p>Callers must pass the time in UTC millisecond (as can be returned |
| 965 | * by {@link #toMillis(boolean)} or {@link #normalize(boolean)}) |
Neil Fuller | 5898cc1 | 2019-08-20 18:23:16 +0100 | [diff] [blame] | 966 | * and the offset from UTC of the timezone in seconds at that time (as |
| 967 | * might be in {@link #gmtoff}). |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 968 | * |
| 969 | * <p>The Julian day is useful for testing if two events occur on the |
| 970 | * same calendar date and for determining the relative time of an event |
| 971 | * from the present ("yesterday", "3 days ago", etc.). |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 972 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 973 | * @param millis the time in UTC milliseconds |
Neil Fuller | 5898cc1 | 2019-08-20 18:23:16 +0100 | [diff] [blame] | 974 | * @param gmtoffSeconds the offset from UTC in seconds |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 975 | * @return the Julian day |
Neil Fuller | 5898cc1 | 2019-08-20 18:23:16 +0100 | [diff] [blame] | 976 | * @deprecated Use {@link java.time.temporal.JulianFields#JULIAN_DAY} instead. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 977 | */ |
Neil Fuller | 5898cc1 | 2019-08-20 18:23:16 +0100 | [diff] [blame] | 978 | @Deprecated |
| 979 | public static int getJulianDay(long millis, long gmtoffSeconds) { |
| 980 | long offsetMillis = gmtoffSeconds * 1000; |
| 981 | long adjustedMillis = millis + offsetMillis; |
| 982 | long julianDay = adjustedMillis / DateUtils.DAY_IN_MILLIS; |
| 983 | // Negative adjustedMillis values must round towards Integer.MIN_VALUE. |
| 984 | if (adjustedMillis < 0 && adjustedMillis % DateUtils.DAY_IN_MILLIS != 0) { |
| 985 | julianDay--; |
| 986 | } |
| 987 | return (int) (julianDay + EPOCH_JULIAN_DAY); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 988 | } |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 989 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 990 | /** |
| 991 | * <p>Sets the time from the given Julian day number, which must be based on |
| 992 | * the same timezone that is set in this Time object. The "gmtoff" field |
| 993 | * need not be initialized because the given Julian day may have a different |
| 994 | * GMT offset than whatever is currently stored in this Time object anyway. |
| 995 | * After this method returns all the fields will be normalized and the time |
| 996 | * will be set to 12am at the beginning of the given Julian day. |
| 997 | * </p> |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 998 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 999 | * <p> |
| 1000 | * The only exception to this is if 12am does not exist for that day because |
| 1001 | * of daylight saving time. For example, Cairo, Eqypt moves time ahead one |
| 1002 | * hour at 12am on April 25, 2008 and there are a few other places that |
| 1003 | * also change daylight saving time at 12am. In those cases, the time |
| 1004 | * will be set to 1am. |
| 1005 | * </p> |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 1006 | * |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1007 | * @param julianDay the Julian day in the timezone for this Time object |
| 1008 | * @return the UTC milliseconds for the beginning of the Julian day |
| 1009 | */ |
| 1010 | public long setJulianDay(int julianDay) { |
| 1011 | // Don't bother with the GMT offset since we don't know the correct |
| 1012 | // value for the given Julian day. Just get close and then adjust |
| 1013 | // the day. |
| 1014 | long millis = (julianDay - EPOCH_JULIAN_DAY) * DateUtils.DAY_IN_MILLIS; |
| 1015 | set(millis); |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 1016 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1017 | // Figure out how close we are to the requested Julian day. |
| 1018 | // We can't be off by more than a day. |
| 1019 | int approximateDay = getJulianDay(millis, gmtoff); |
| 1020 | int diff = julianDay - approximateDay; |
| 1021 | monthDay += diff; |
Christian Mehlmauer | 0df10e9 | 2010-07-06 20:42:22 +0200 | [diff] [blame] | 1022 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1023 | // Set the time to 12am and re-normalize. |
| 1024 | hour = 0; |
| 1025 | minute = 0; |
| 1026 | second = 0; |
| 1027 | millis = normalize(true); |
| 1028 | return millis; |
| 1029 | } |
Svetoslav Ganov | 50f34d1 | 2010-12-03 16:05:40 -0800 | [diff] [blame] | 1030 | |
| 1031 | /** |
| 1032 | * Returns the week since {@link #EPOCH_JULIAN_DAY} (Jan 1, 1970) adjusted |
| 1033 | * for first day of week. This takes a julian day and the week start day and |
| 1034 | * calculates which week since {@link #EPOCH_JULIAN_DAY} that day occurs in, |
| 1035 | * starting at 0. *Do not* use this to compute the ISO week number for the |
| 1036 | * year. |
| 1037 | * |
| 1038 | * @param julianDay The julian day to calculate the week number for |
| 1039 | * @param firstDayOfWeek Which week day is the first day of the week, see |
| 1040 | * {@link #SUNDAY} |
| 1041 | * @return Weeks since the epoch |
| 1042 | */ |
| 1043 | public static int getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek) { |
| 1044 | int diff = THURSDAY - firstDayOfWeek; |
| 1045 | if (diff < 0) { |
| 1046 | diff += 7; |
| 1047 | } |
| 1048 | int refDay = EPOCH_JULIAN_DAY - diff; |
| 1049 | return (julianDay - refDay) / 7; |
| 1050 | } |
| 1051 | |
| 1052 | /** |
| 1053 | * Takes a number of weeks since the epoch and calculates the Julian day of |
| 1054 | * the Monday for that week. This assumes that the week containing the |
| 1055 | * {@link #EPOCH_JULIAN_DAY} is considered week 0. It returns the Julian day |
| 1056 | * for the Monday week weeks after the Monday of the week containing the |
| 1057 | * epoch. |
| 1058 | * |
| 1059 | * @param week Number of weeks since the epoch |
| 1060 | * @return The julian day for the Monday of the given week since the epoch |
| 1061 | */ |
| 1062 | public static int getJulianMondayFromWeeksSinceEpoch(int week) { |
| 1063 | return MONDAY_BEFORE_JULIAN_EPOCH + week * 7; |
| 1064 | } |
Neil Fuller | d7f0849 | 2014-06-25 11:13:25 +0100 | [diff] [blame] | 1065 | |
| 1066 | /** |
| 1067 | * A class that handles date/time calculations. |
| 1068 | * |
| 1069 | * This class originated as a port of a native C++ class ("android.Time") to pure Java. It is |
| 1070 | * separate from the enclosing class because some methods copy the result of calculations back |
| 1071 | * to the enclosing object, but others do not: thus separate state is retained. |
| 1072 | */ |
| 1073 | private static class TimeCalculator { |
| 1074 | public final ZoneInfo.WallTime wallTime; |
| 1075 | public String timezone; |
| 1076 | |
| 1077 | // Information about the current timezone. |
| 1078 | private ZoneInfo zoneInfo; |
| 1079 | |
| 1080 | public TimeCalculator(String timezoneId) { |
| 1081 | this.zoneInfo = lookupZoneInfo(timezoneId); |
| 1082 | this.wallTime = new ZoneInfo.WallTime(); |
| 1083 | } |
| 1084 | |
| 1085 | public long toMillis(boolean ignoreDst) { |
| 1086 | if (ignoreDst) { |
| 1087 | wallTime.setIsDst(-1); |
| 1088 | } |
| 1089 | |
| 1090 | int r = wallTime.mktime(zoneInfo); |
| 1091 | if (r == -1) { |
| 1092 | return -1; |
| 1093 | } |
| 1094 | return r * 1000L; |
| 1095 | } |
| 1096 | |
| 1097 | public void setTimeInMillis(long millis) { |
| 1098 | // Preserve old 32-bit Android behavior. |
| 1099 | int intSeconds = (int) (millis / 1000); |
| 1100 | |
| 1101 | updateZoneInfoFromTimeZone(); |
| 1102 | wallTime.localtime(intSeconds, zoneInfo); |
| 1103 | } |
| 1104 | |
| 1105 | public String format(String format) { |
| 1106 | if (format == null) { |
| 1107 | format = "%c"; |
| 1108 | } |
| 1109 | TimeFormatter formatter = new TimeFormatter(); |
| 1110 | return formatter.format(format, wallTime, zoneInfo); |
| 1111 | } |
| 1112 | |
| 1113 | private void updateZoneInfoFromTimeZone() { |
| 1114 | if (!zoneInfo.getID().equals(timezone)) { |
| 1115 | this.zoneInfo = lookupZoneInfo(timezone); |
| 1116 | } |
| 1117 | } |
| 1118 | |
| 1119 | private static ZoneInfo lookupZoneInfo(String timezoneId) { |
| 1120 | try { |
| 1121 | ZoneInfo zoneInfo = ZoneInfoDB.getInstance().makeTimeZone(timezoneId); |
| 1122 | if (zoneInfo == null) { |
| 1123 | zoneInfo = ZoneInfoDB.getInstance().makeTimeZone("GMT"); |
| 1124 | } |
| 1125 | if (zoneInfo == null) { |
| 1126 | throw new AssertionError("GMT not found: \"" + timezoneId + "\""); |
| 1127 | } |
| 1128 | return zoneInfo; |
| 1129 | } catch (IOException e) { |
| 1130 | // This should not ever be thrown. |
| 1131 | throw new AssertionError("Error loading timezone: \"" + timezoneId + "\"", e); |
| 1132 | } |
| 1133 | } |
| 1134 | |
| 1135 | public void switchTimeZone(String timezone) { |
| 1136 | int seconds = wallTime.mktime(zoneInfo); |
| 1137 | this.timezone = timezone; |
| 1138 | updateZoneInfoFromTimeZone(); |
| 1139 | wallTime.localtime(seconds, zoneInfo); |
| 1140 | } |
| 1141 | |
| 1142 | public String format2445(boolean hasTime) { |
| 1143 | char[] buf = new char[hasTime ? 16 : 8]; |
| 1144 | int n = wallTime.getYear(); |
| 1145 | |
| 1146 | buf[0] = toChar(n / 1000); |
| 1147 | n %= 1000; |
| 1148 | buf[1] = toChar(n / 100); |
| 1149 | n %= 100; |
| 1150 | buf[2] = toChar(n / 10); |
| 1151 | n %= 10; |
| 1152 | buf[3] = toChar(n); |
| 1153 | |
| 1154 | n = wallTime.getMonth() + 1; |
| 1155 | buf[4] = toChar(n / 10); |
| 1156 | buf[5] = toChar(n % 10); |
| 1157 | |
| 1158 | n = wallTime.getMonthDay(); |
| 1159 | buf[6] = toChar(n / 10); |
| 1160 | buf[7] = toChar(n % 10); |
| 1161 | |
| 1162 | if (!hasTime) { |
| 1163 | return new String(buf, 0, 8); |
| 1164 | } |
| 1165 | |
| 1166 | buf[8] = 'T'; |
| 1167 | |
| 1168 | n = wallTime.getHour(); |
| 1169 | buf[9] = toChar(n / 10); |
| 1170 | buf[10] = toChar(n % 10); |
| 1171 | |
| 1172 | n = wallTime.getMinute(); |
| 1173 | buf[11] = toChar(n / 10); |
| 1174 | buf[12] = toChar(n % 10); |
| 1175 | |
| 1176 | n = wallTime.getSecond(); |
| 1177 | buf[13] = toChar(n / 10); |
| 1178 | buf[14] = toChar(n % 10); |
| 1179 | |
| 1180 | if (TIMEZONE_UTC.equals(timezone)) { |
| 1181 | // The letter 'Z' is appended to the end. |
| 1182 | buf[15] = 'Z'; |
| 1183 | return new String(buf, 0, 16); |
| 1184 | } else { |
| 1185 | return new String(buf, 0, 15); |
| 1186 | } |
| 1187 | } |
| 1188 | |
| 1189 | private char toChar(int n) { |
| 1190 | return (n >= 0 && n <= 9) ? (char) (n + '0') : ' '; |
| 1191 | } |
| 1192 | |
| 1193 | /** |
| 1194 | * A method that will return the state of this object in string form. Note: it has side |
| 1195 | * effects and so has deliberately not been made the default {@link #toString()}. |
| 1196 | */ |
| 1197 | public String toStringInternal() { |
| 1198 | // This implementation possibly displays the un-normalized fields because that is |
| 1199 | // what it has always done. |
| 1200 | return String.format("%04d%02d%02dT%02d%02d%02d%s(%d,%d,%d,%d,%d)", |
| 1201 | wallTime.getYear(), |
| 1202 | wallTime.getMonth() + 1, |
| 1203 | wallTime.getMonthDay(), |
| 1204 | wallTime.getHour(), |
| 1205 | wallTime.getMinute(), |
| 1206 | wallTime.getSecond(), |
| 1207 | timezone, |
| 1208 | wallTime.getWeekDay(), |
| 1209 | wallTime.getYearDay(), |
| 1210 | wallTime.getGmtOffset(), |
| 1211 | wallTime.getIsDst(), |
| 1212 | toMillis(false /* use isDst */) / 1000 |
| 1213 | ); |
| 1214 | |
| 1215 | } |
| 1216 | |
| 1217 | public static int compare(TimeCalculator aObject, TimeCalculator bObject) { |
| 1218 | if (aObject.timezone.equals(bObject.timezone)) { |
| 1219 | // If the timezones are the same, we can easily compare the two times. |
| 1220 | int diff = aObject.wallTime.getYear() - bObject.wallTime.getYear(); |
| 1221 | if (diff != 0) { |
| 1222 | return diff; |
| 1223 | } |
| 1224 | |
| 1225 | diff = aObject.wallTime.getMonth() - bObject.wallTime.getMonth(); |
| 1226 | if (diff != 0) { |
| 1227 | return diff; |
| 1228 | } |
| 1229 | |
| 1230 | diff = aObject.wallTime.getMonthDay() - bObject.wallTime.getMonthDay(); |
| 1231 | if (diff != 0) { |
| 1232 | return diff; |
| 1233 | } |
| 1234 | |
| 1235 | diff = aObject.wallTime.getHour() - bObject.wallTime.getHour(); |
| 1236 | if (diff != 0) { |
| 1237 | return diff; |
| 1238 | } |
| 1239 | |
| 1240 | diff = aObject.wallTime.getMinute() - bObject.wallTime.getMinute(); |
| 1241 | if (diff != 0) { |
| 1242 | return diff; |
| 1243 | } |
| 1244 | |
| 1245 | diff = aObject.wallTime.getSecond() - bObject.wallTime.getSecond(); |
| 1246 | if (diff != 0) { |
| 1247 | return diff; |
| 1248 | } |
| 1249 | |
| 1250 | return 0; |
| 1251 | } else { |
| 1252 | // Otherwise, convert to milliseconds and compare that. This requires that object be |
| 1253 | // normalized. Note: For dates that do not exist: toMillis() can return -1, which |
| 1254 | // can be confused with a valid time. |
| 1255 | long am = aObject.toMillis(false /* use isDst */); |
| 1256 | long bm = bObject.toMillis(false /* use isDst */); |
| 1257 | long diff = am - bm; |
| 1258 | return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0); |
| 1259 | } |
| 1260 | |
| 1261 | } |
| 1262 | |
| 1263 | public void copyFieldsToTime(Time time) { |
| 1264 | time.second = wallTime.getSecond(); |
| 1265 | time.minute = wallTime.getMinute(); |
| 1266 | time.hour = wallTime.getHour(); |
| 1267 | time.monthDay = wallTime.getMonthDay(); |
| 1268 | time.month = wallTime.getMonth(); |
| 1269 | time.year = wallTime.getYear(); |
| 1270 | |
| 1271 | // Read-only fields that are derived from other information above. |
| 1272 | time.weekDay = wallTime.getWeekDay(); |
| 1273 | time.yearDay = wallTime.getYearDay(); |
| 1274 | |
| 1275 | // < 0: DST status unknown, 0: is not in DST, 1: is in DST |
| 1276 | time.isDst = wallTime.getIsDst(); |
| 1277 | // This is in seconds and includes any DST offset too. |
| 1278 | time.gmtoff = wallTime.getGmtOffset(); |
| 1279 | } |
| 1280 | |
| 1281 | public void copyFieldsFromTime(Time time) { |
| 1282 | wallTime.setSecond(time.second); |
| 1283 | wallTime.setMinute(time.minute); |
| 1284 | wallTime.setHour(time.hour); |
| 1285 | wallTime.setMonthDay(time.monthDay); |
| 1286 | wallTime.setMonth(time.month); |
| 1287 | wallTime.setYear(time.year); |
| 1288 | wallTime.setWeekDay(time.weekDay); |
| 1289 | wallTime.setYearDay(time.yearDay); |
| 1290 | wallTime.setIsDst(time.isDst); |
| 1291 | wallTime.setGmtOffset((int) time.gmtoff); |
| 1292 | |
| 1293 | if (time.allDay && (time.second != 0 || time.minute != 0 || time.hour != 0)) { |
| 1294 | throw new IllegalArgumentException("allDay is true but sec, min, hour are not 0."); |
| 1295 | } |
| 1296 | |
| 1297 | timezone = time.timezone; |
| 1298 | updateZoneInfoFromTimeZone(); |
| 1299 | } |
| 1300 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1301 | } |