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 19 import android.content.res.Resources; 20 21 import java.util.Locale; 22 import java.util.TimeZone; 23 24 /** 25 * The Time class is a faster replacement for the java.util.Calendar and 26 * java.util.GregorianCalendar classes. An instance of the Time class represents 27 * a moment in time, specified with second precision. It is modelled after 28 * struct tm, and in fact, uses struct tm to implement most of the 29 * functionality. 30 */ 31 public class Time { 32 private static final String Y_M_D_T_H_M_S_000 = "%Y-%m-%dT%H:%M:%S.000"; 33 private static final String Y_M_D_T_H_M_S_000_Z = "%Y-%m-%dT%H:%M:%S.000Z"; 34 private static final String Y_M_D = "%Y-%m-%d"; 35 36 public static final String TIMEZONE_UTC = "UTC"; 37 38 /** 39 * The Julian day of the epoch, that is, January 1, 1970 on the Gregorian 40 * calendar. 41 */ 42 public static final int EPOCH_JULIAN_DAY = 2440588; 43 44 /** 45 * True if this is an allDay event. The hour, minute, second fields are 46 * all zero, and the date is displayed the same in all time zones. 47 */ 48 public boolean allDay; 49 50 /** 51 * Seconds [0-61] (2 leap seconds allowed) 52 */ 53 public int second; 54 55 /** 56 * Minute [0-59] 57 */ 58 public int minute; 59 60 /** 61 * Hour of day [0-23] 62 */ 63 public int hour; 64 65 /** 66 * Day of month [1-31] 67 */ 68 public int monthDay; 69 70 /** 71 * Month [0-11] 72 */ 73 public int month; 74 75 /** 76 * Year. TBD. Is this years since 1900 like in struct tm? 77 */ 78 public int year; 79 80 /** 81 * Day of week [0-6] 82 */ 83 public int weekDay; 84 85 /** 86 * Day of year [0-365] 87 */ 88 public int yearDay; 89 90 /** 91 * This time is in daylight savings time. One of: 92 * <ul> 93 * <li><b>positive</b> - in dst</li> 94 * <li><b>0</b> - not in dst</li> 95 * <li><b>negative</b> - unknown</li> 96 * </ul> 97 */ 98 public int isDst; 99 100 /** 101 * Offset from UTC (in seconds). 102 */ 103 public long gmtoff; 104 105 /** 106 * The timezone for this Time. Should not be null. 107 */ 108 public String timezone; 109 110 /* 111 * Define symbolic constants for accessing the fields in this class. Used in 112 * getActualMaximum(). 113 */ 114 public static final int SECOND = 1; 115 public static final int MINUTE = 2; 116 public static final int HOUR = 3; 117 public static final int MONTH_DAY = 4; 118 public static final int MONTH = 5; 119 public static final int YEAR = 6; 120 public static final int WEEK_DAY = 7; 121 public static final int YEAR_DAY = 8; 122 public static final int WEEK_NUM = 9; 123 124 public static final int SUNDAY = 0; 125 public static final int MONDAY = 1; 126 public static final int TUESDAY = 2; 127 public static final int WEDNESDAY = 3; 128 public static final int THURSDAY = 4; 129 public static final int FRIDAY = 5; 130 public static final int SATURDAY = 6; 131 132 /* 133 * The Locale for which date formatting strings have been loaded. 134 */ 135 private static Locale sLocale; 136 private static String[] sShortMonths; 137 private static String[] sLongMonths; 138 private static String[] sLongStandaloneMonths; 139 private static String[] sShortWeekdays; 140 private static String[] sLongWeekdays; 141 private static String sTimeOnlyFormat; 142 private static String sDateOnlyFormat; 143 private static String sDateTimeFormat; 144 private static String sAm; 145 private static String sPm; 146 private static String sDateCommand = "%a %b %e %H:%M:%S %Z %Y"; 147 148 /** 149 * Construct a Time object in the timezone named by the string 150 * argument "timezone". The time is initialized to Jan 1, 1970. 151 * @param timezone string containing the timezone to use. 152 * @see TimeZone 153 */ Time(String timezone)154 public Time(String timezone) { 155 if (timezone == null) { 156 throw new NullPointerException("timezone is null!"); 157 } 158 this.timezone = timezone; 159 this.year = 1970; 160 this.monthDay = 1; 161 // Set the daylight-saving indicator to the unknown value -1 so that 162 // it will be recomputed. 163 this.isDst = -1; 164 } 165 166 /** 167 * Construct a Time object in the default timezone. The time is initialized to 168 * Jan 1, 1970. 169 */ Time()170 public Time() { 171 this(TimeZone.getDefault().getID()); 172 } 173 174 /** 175 * A copy constructor. Construct a Time object by copying the given 176 * Time object. No normalization occurs. 177 * 178 * @param other 179 */ Time(Time other)180 public Time(Time other) { 181 set(other); 182 } 183 184 /** 185 * Ensures the values in each field are in range. For example if the 186 * current value of this calendar is March 32, normalize() will convert it 187 * to April 1. It also fills in weekDay, yearDay, isDst and gmtoff. 188 * 189 * <p> 190 * If "ignoreDst" is true, then this method sets the "isDst" field to -1 191 * (the "unknown" value) before normalizing. It then computes the 192 * correct value for "isDst". 193 * 194 * <p> 195 * See {@link #toMillis(boolean)} for more information about when to 196 * use <tt>true</tt> or <tt>false</tt> for "ignoreDst". 197 * 198 * @return the UTC milliseconds since the epoch 199 */ normalize(boolean ignoreDst)200 native public long normalize(boolean ignoreDst); 201 202 /** 203 * Convert this time object so the time represented remains the same, but is 204 * instead located in a different timezone. This method automatically calls 205 * normalize() in some cases 206 */ switchTimezone(String timezone)207 native public void switchTimezone(String timezone); 208 209 private static final int[] DAYS_PER_MONTH = { 31, 28, 31, 30, 31, 30, 31, 210 31, 30, 31, 30, 31 }; 211 212 /** 213 * Return the maximum possible value for the given field given the value of 214 * the other fields. Requires that it be normalized for MONTH_DAY and 215 * YEAR_DAY. 216 * @param field one of the constants for HOUR, MINUTE, SECOND, etc. 217 * @return the maximum value for the field. 218 */ getActualMaximum(int field)219 public int getActualMaximum(int field) { 220 switch (field) { 221 case SECOND: 222 return 59; // leap seconds, bah humbug 223 case MINUTE: 224 return 59; 225 case HOUR: 226 return 23; 227 case MONTH_DAY: { 228 int n = DAYS_PER_MONTH[this.month]; 229 if (n != 28) { 230 return n; 231 } else { 232 int y = this.year; 233 return ((y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0)) ? 29 : 28; 234 } 235 } 236 case MONTH: 237 return 11; 238 case YEAR: 239 return 2037; 240 case WEEK_DAY: 241 return 6; 242 case YEAR_DAY: { 243 int y = this.year; 244 // Year days are numbered from 0, so the last one is usually 364. 245 return ((y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0)) ? 365 : 364; 246 } 247 case WEEK_NUM: 248 throw new RuntimeException("WEEK_NUM not implemented"); 249 default: 250 throw new RuntimeException("bad field=" + field); 251 } 252 } 253 254 /** 255 * Clears all values, setting the timezone to the given timezone. Sets isDst 256 * to a negative value to mean "unknown". 257 * @param timezone the timezone to use. 258 */ clear(String timezone)259 public void clear(String timezone) { 260 if (timezone == null) { 261 throw new NullPointerException("timezone is null!"); 262 } 263 this.timezone = timezone; 264 this.allDay = false; 265 this.second = 0; 266 this.minute = 0; 267 this.hour = 0; 268 this.monthDay = 0; 269 this.month = 0; 270 this.year = 0; 271 this.weekDay = 0; 272 this.yearDay = 0; 273 this.gmtoff = 0; 274 this.isDst = -1; 275 } 276 277 /** 278 * return a negative number if a is less than b, a positive number if a is 279 * greater than b, and 0 if they are equal. 280 */ compare(Time a, Time b)281 native public static int compare(Time a, Time b); 282 283 /** 284 * Print the current value given the format string provided. See man 285 * strftime for what means what. The final string must be less than 256 286 * characters. 287 * @param format a string containing the desired format. 288 * @return a String containing the current time expressed in the current locale. 289 */ format(String format)290 public String format(String format) { 291 synchronized (Time.class) { 292 Locale locale = Locale.getDefault(); 293 294 if (sLocale == null || locale == null || !(locale.equals(sLocale))) { 295 Resources r = Resources.getSystem(); 296 297 sShortMonths = new String[] { 298 r.getString(com.android.internal.R.string.month_medium_january), 299 r.getString(com.android.internal.R.string.month_medium_february), 300 r.getString(com.android.internal.R.string.month_medium_march), 301 r.getString(com.android.internal.R.string.month_medium_april), 302 r.getString(com.android.internal.R.string.month_medium_may), 303 r.getString(com.android.internal.R.string.month_medium_june), 304 r.getString(com.android.internal.R.string.month_medium_july), 305 r.getString(com.android.internal.R.string.month_medium_august), 306 r.getString(com.android.internal.R.string.month_medium_september), 307 r.getString(com.android.internal.R.string.month_medium_october), 308 r.getString(com.android.internal.R.string.month_medium_november), 309 r.getString(com.android.internal.R.string.month_medium_december), 310 }; 311 sLongMonths = new String[] { 312 r.getString(com.android.internal.R.string.month_long_january), 313 r.getString(com.android.internal.R.string.month_long_february), 314 r.getString(com.android.internal.R.string.month_long_march), 315 r.getString(com.android.internal.R.string.month_long_april), 316 r.getString(com.android.internal.R.string.month_long_may), 317 r.getString(com.android.internal.R.string.month_long_june), 318 r.getString(com.android.internal.R.string.month_long_july), 319 r.getString(com.android.internal.R.string.month_long_august), 320 r.getString(com.android.internal.R.string.month_long_september), 321 r.getString(com.android.internal.R.string.month_long_october), 322 r.getString(com.android.internal.R.string.month_long_november), 323 r.getString(com.android.internal.R.string.month_long_december), 324 }; 325 sLongStandaloneMonths = new String[] { 326 r.getString(com.android.internal.R.string.month_long_standalone_january), 327 r.getString(com.android.internal.R.string.month_long_standalone_february), 328 r.getString(com.android.internal.R.string.month_long_standalone_march), 329 r.getString(com.android.internal.R.string.month_long_standalone_april), 330 r.getString(com.android.internal.R.string.month_long_standalone_may), 331 r.getString(com.android.internal.R.string.month_long_standalone_june), 332 r.getString(com.android.internal.R.string.month_long_standalone_july), 333 r.getString(com.android.internal.R.string.month_long_standalone_august), 334 r.getString(com.android.internal.R.string.month_long_standalone_september), 335 r.getString(com.android.internal.R.string.month_long_standalone_october), 336 r.getString(com.android.internal.R.string.month_long_standalone_november), 337 r.getString(com.android.internal.R.string.month_long_standalone_december), 338 }; 339 sShortWeekdays = new String[] { 340 r.getString(com.android.internal.R.string.day_of_week_medium_sunday), 341 r.getString(com.android.internal.R.string.day_of_week_medium_monday), 342 r.getString(com.android.internal.R.string.day_of_week_medium_tuesday), 343 r.getString(com.android.internal.R.string.day_of_week_medium_wednesday), 344 r.getString(com.android.internal.R.string.day_of_week_medium_thursday), 345 r.getString(com.android.internal.R.string.day_of_week_medium_friday), 346 r.getString(com.android.internal.R.string.day_of_week_medium_saturday), 347 }; 348 sLongWeekdays = new String[] { 349 r.getString(com.android.internal.R.string.day_of_week_long_sunday), 350 r.getString(com.android.internal.R.string.day_of_week_long_monday), 351 r.getString(com.android.internal.R.string.day_of_week_long_tuesday), 352 r.getString(com.android.internal.R.string.day_of_week_long_wednesday), 353 r.getString(com.android.internal.R.string.day_of_week_long_thursday), 354 r.getString(com.android.internal.R.string.day_of_week_long_friday), 355 r.getString(com.android.internal.R.string.day_of_week_long_saturday), 356 }; 357 sTimeOnlyFormat = r.getString(com.android.internal.R.string.time_of_day); 358 sDateOnlyFormat = r.getString(com.android.internal.R.string.month_day_year); 359 sDateTimeFormat = r.getString(com.android.internal.R.string.date_and_time); 360 sAm = r.getString(com.android.internal.R.string.am); 361 sPm = r.getString(com.android.internal.R.string.pm); 362 363 sLocale = locale; 364 } 365 366 return format1(format); 367 } 368 } 369 format1(String format)370 native private String format1(String format); 371 372 /** 373 * Return the current time in YYYYMMDDTHHMMSS<tz> format 374 */ 375 @Override toString()376 native public String toString(); 377 378 /** 379 * Parses a date-time string in either the RFC 2445 format or an abbreviated 380 * format that does not include the "time" field. For example, all of the 381 * following strings are valid: 382 * 383 * <ul> 384 * <li>"20081013T160000Z"</li> 385 * <li>"20081013T160000"</li> 386 * <li>"20081013"</li> 387 * </ul> 388 * 389 * Returns whether or not the time is in UTC (ends with Z). If the string 390 * ends with "Z" then the timezone is set to UTC. If the date-time string 391 * included only a date and no time field, then the <code>allDay</code> 392 * field of this Time class is set to true and the <code>hour</code>, 393 * <code>minute</code>, and <code>second</code> fields are set to zero; 394 * otherwise (a time field was included in the date-time string) 395 * <code>allDay</code> is set to false. The fields <code>weekDay</code>, 396 * <code>yearDay</code>, and <code>gmtoff</code> are always set to zero, 397 * and the field <code>isDst</code> is set to -1 (unknown). To set those 398 * fields, call {@link #normalize(boolean)} after parsing. 399 * 400 * To parse a date-time string and convert it to UTC milliseconds, do 401 * something like this: 402 * 403 * <pre> 404 * Time time = new Time(); 405 * String date = "20081013T160000Z"; 406 * time.parse(date); 407 * long millis = time.normalize(false); 408 * </pre> 409 * 410 * @param s the string to parse 411 * @return true if the resulting time value is in UTC time 412 * @throws android.util.TimeFormatException if s cannot be parsed. 413 */ parse(String s)414 public boolean parse(String s) { 415 if (nativeParse(s)) { 416 timezone = TIMEZONE_UTC; 417 return true; 418 } 419 return false; 420 } 421 422 /** 423 * Parse a time in the current zone in YYYYMMDDTHHMMSS format. 424 */ nativeParse(String s)425 native private boolean nativeParse(String s); 426 427 /** 428 * Parse a time in RFC 3339 format. This method also parses simple dates 429 * (that is, strings that contain no time or time offset). For example, 430 * all of the following strings are valid: 431 * 432 * <ul> 433 * <li>"2008-10-13T16:00:00.000Z"</li> 434 * <li>"2008-10-13T16:00:00.000+07:00"</li> 435 * <li>"2008-10-13T16:00:00.000-07:00"</li> 436 * <li>"2008-10-13"</li> 437 * </ul> 438 * 439 * <p> 440 * If the string contains a time and time offset, then the time offset will 441 * be used to convert the time value to UTC. 442 * </p> 443 * 444 * <p> 445 * If the given string contains just a date (with no time field), then 446 * the {@link #allDay} field is set to true and the {@link #hour}, 447 * {@link #minute}, and {@link #second} fields are set to zero. 448 * </p> 449 * 450 * <p> 451 * Returns true if the resulting time value is in UTC time. 452 * </p> 453 * 454 * @param s the string to parse 455 * @return true if the resulting time value is in UTC time 456 * @throws android.util.TimeFormatException if s cannot be parsed. 457 */ parse3339(String s)458 public boolean parse3339(String s) { 459 if (nativeParse3339(s)) { 460 timezone = TIMEZONE_UTC; 461 return true; 462 } 463 return false; 464 } 465 nativeParse3339(String s)466 native private boolean nativeParse3339(String s); 467 468 /** 469 * Returns the timezone string that is currently set for the device. 470 */ getCurrentTimezone()471 public static String getCurrentTimezone() { 472 return TimeZone.getDefault().getID(); 473 } 474 475 /** 476 * Sets the time of the given Time object to the current time. 477 */ setToNow()478 native public void setToNow(); 479 480 /** 481 * Converts this time to milliseconds. Suitable for interacting with the 482 * standard java libraries. The time is in UTC milliseconds since the epoch. 483 * This does an implicit normalization to compute the milliseconds but does 484 * <em>not</em> change any of the fields in this Time object. If you want 485 * to normalize the fields in this Time object and also get the milliseconds 486 * then use {@link #normalize(boolean)}. 487 * 488 * <p> 489 * If "ignoreDst" is false, then this method uses the current setting of the 490 * "isDst" field and will adjust the returned time if the "isDst" field is 491 * wrong for the given time. See the sample code below for an example of 492 * this. 493 * 494 * <p> 495 * If "ignoreDst" is true, then this method ignores the current setting of 496 * the "isDst" field in this Time object and will instead figure out the 497 * correct value of "isDst" (as best it can) from the fields in this 498 * Time object. The only case where this method cannot figure out the 499 * correct value of the "isDst" field is when the time is inherently 500 * ambiguous because it falls in the hour that is repeated when switching 501 * from Daylight-Saving Time to Standard Time. 502 * 503 * <p> 504 * Here is an example where <tt>toMillis(true)</tt> adjusts the time, 505 * assuming that DST changes at 2am on Sunday, Nov 4, 2007. 506 * 507 * <pre> 508 * Time time = new Time(); 509 * time.set(2007, 10, 4); // set the date to Nov 4, 2007, 12am 510 * time.normalize(); // this sets isDst = 1 511 * time.monthDay += 1; // changes the date to Nov 5, 2007, 12am 512 * millis = time.toMillis(false); // millis is Nov 4, 2007, 11pm 513 * millis = time.toMillis(true); // millis is Nov 5, 2007, 12am 514 * </pre> 515 * 516 * <p> 517 * To avoid this problem, use <tt>toMillis(true)</tt> 518 * after adding or subtracting days or explicitly setting the "monthDay" 519 * field. On the other hand, if you are adding 520 * or subtracting hours or minutes, then you should use 521 * <tt>toMillis(false)</tt>. 522 * 523 * <p> 524 * You should also use <tt>toMillis(false)</tt> if you want 525 * to read back the same milliseconds that you set with {@link #set(long)} 526 * or {@link #set(Time)} or after parsing a date string. 527 */ toMillis(boolean ignoreDst)528 native public long toMillis(boolean ignoreDst); 529 530 /** 531 * Sets the fields in this Time object given the UTC milliseconds. After 532 * this method returns, all the fields are normalized. 533 * This also sets the "isDst" field to the correct value. 534 * 535 * @param millis the time in UTC milliseconds since the epoch. 536 */ set(long millis)537 native public void set(long millis); 538 539 /** 540 * Format according to RFC 2445 DATETIME type. 541 * 542 * <p> 543 * The same as format("%Y%m%dT%H%M%S"). 544 */ format2445()545 native public String format2445(); 546 547 /** 548 * Copy the value of that to this Time object. No normalization happens. 549 */ set(Time that)550 public void set(Time that) { 551 this.timezone = that.timezone; 552 this.allDay = that.allDay; 553 this.second = that.second; 554 this.minute = that.minute; 555 this.hour = that.hour; 556 this.monthDay = that.monthDay; 557 this.month = that.month; 558 this.year = that.year; 559 this.weekDay = that.weekDay; 560 this.yearDay = that.yearDay; 561 this.isDst = that.isDst; 562 this.gmtoff = that.gmtoff; 563 } 564 565 /** 566 * Sets the fields. Sets weekDay, yearDay and gmtoff to 0, and isDst to -1. 567 * Call {@link #normalize(boolean)} if you need those. 568 */ set(int second, int minute, int hour, int monthDay, int month, int year)569 public void set(int second, int minute, int hour, int monthDay, int month, int year) { 570 this.allDay = false; 571 this.second = second; 572 this.minute = minute; 573 this.hour = hour; 574 this.monthDay = monthDay; 575 this.month = month; 576 this.year = year; 577 this.weekDay = 0; 578 this.yearDay = 0; 579 this.isDst = -1; 580 this.gmtoff = 0; 581 } 582 583 /** 584 * Sets the date from the given fields. Also sets allDay to true. 585 * Sets weekDay, yearDay and gmtoff to 0, and isDst to -1. 586 * Call {@link #normalize(boolean)} if you need those. 587 * 588 * @param monthDay the day of the month (in the range [1,31]) 589 * @param month the zero-based month number (in the range [0,11]) 590 * @param year the year 591 */ set(int monthDay, int month, int year)592 public void set(int monthDay, int month, int year) { 593 this.allDay = true; 594 this.second = 0; 595 this.minute = 0; 596 this.hour = 0; 597 this.monthDay = monthDay; 598 this.month = month; 599 this.year = year; 600 this.weekDay = 0; 601 this.yearDay = 0; 602 this.isDst = -1; 603 this.gmtoff = 0; 604 } 605 606 /** 607 * Returns true if the time represented by this Time object occurs before 608 * the given time. 609 * 610 * @param that a given Time object to compare against 611 * @return true if this time is less than the given time 612 */ before(Time that)613 public boolean before(Time that) { 614 return Time.compare(this, that) < 0; 615 } 616 617 618 /** 619 * Returns true if the time represented by this Time object occurs after 620 * the given time. 621 * 622 * @param that a given Time object to compare against 623 * @return true if this time is greater than the given time 624 */ after(Time that)625 public boolean after(Time that) { 626 return Time.compare(this, that) > 0; 627 } 628 629 /** 630 * This array is indexed by the weekDay field (SUNDAY=0, MONDAY=1, etc.) 631 * and gives a number that can be added to the yearDay to give the 632 * closest Thursday yearDay. 633 */ 634 private static final int[] sThursdayOffset = { -3, 3, 2, 1, 0, -1, -2 }; 635 636 /** 637 * Computes the week number according to ISO 8601. The current Time 638 * object must already be normalized because this method uses the 639 * yearDay and weekDay fields. 640 * 641 * <p> 642 * In IS0 8601, weeks start on Monday. 643 * The first week of the year (week 1) is defined by ISO 8601 as the 644 * first week with four or more of its days in the starting year. 645 * Or equivalently, the week containing January 4. Or equivalently, 646 * the week with the year's first Thursday in it. 647 * </p> 648 * 649 * <p> 650 * The week number can be calculated by counting Thursdays. Week N 651 * contains the Nth Thursday of the year. 652 * </p> 653 * 654 * @return the ISO week number. 655 */ getWeekNumber()656 public int getWeekNumber() { 657 // Get the year day for the closest Thursday 658 int closestThursday = yearDay + sThursdayOffset[weekDay]; 659 660 // Year days start at 0 661 if (closestThursday >= 0 && closestThursday <= 364) { 662 return closestThursday / 7 + 1; 663 } 664 665 // The week crosses a year boundary. 666 Time temp = new Time(this); 667 temp.monthDay += sThursdayOffset[weekDay]; 668 temp.normalize(true /* ignore isDst */); 669 return temp.yearDay / 7 + 1; 670 } 671 672 /** 673 * Return a string in the RFC 3339 format. 674 * <p> 675 * If allDay is true, expresses the time as Y-M-D</p> 676 * <p> 677 * Otherwise, if the timezone is UTC, expresses the time as Y-M-D-T-H-M-S UTC</p> 678 * <p> 679 * Otherwise the time is expressed the time as Y-M-D-T-H-M-S +- GMT</p> 680 * @param allDay 681 * @return string in the RFC 3339 format. 682 */ format3339(boolean allDay)683 public String format3339(boolean allDay) { 684 if (allDay) { 685 return format(Y_M_D); 686 } else if (TIMEZONE_UTC.equals(timezone)) { 687 return format(Y_M_D_T_H_M_S_000_Z); 688 } else { 689 String base = format(Y_M_D_T_H_M_S_000); 690 String sign = (gmtoff < 0) ? "-" : "+"; 691 int offset = (int)Math.abs(gmtoff); 692 int minutes = (offset % 3600) / 60; 693 int hours = offset / 3600; 694 695 return String.format("%s%s%02d:%02d", base, sign, hours, minutes); 696 } 697 } 698 699 /** 700 * Returns true if the day of the given time is the epoch on the Julian Calendar 701 * (January 1, 1970 on the Gregorian calendar). 702 * 703 * @param time the time to test 704 * @return true if epoch. 705 */ isEpoch(Time time)706 public static boolean isEpoch(Time time) { 707 long millis = time.toMillis(true); 708 return getJulianDay(millis, 0) == EPOCH_JULIAN_DAY; 709 } 710 711 /** 712 * Computes the Julian day number, given the UTC milliseconds 713 * and the offset (in seconds) from UTC. The Julian day for a given 714 * date will be the same for every timezone. For example, the Julian 715 * day for July 1, 2008 is 2454649. This is the same value no matter 716 * what timezone is being used. The Julian day is useful for testing 717 * if two events occur on the same day and for determining the relative 718 * time of an event from the present ("yesterday", "3 days ago", etc.). 719 * 720 * <p> 721 * Use {@link #toMillis(boolean)} to get the milliseconds. 722 * 723 * @param millis the time in UTC milliseconds 724 * @param gmtoff the offset from UTC in seconds 725 * @return the Julian day 726 */ getJulianDay(long millis, long gmtoff)727 public static int getJulianDay(long millis, long gmtoff) { 728 long offsetMillis = gmtoff * 1000; 729 long julianDay = (millis + offsetMillis) / DateUtils.DAY_IN_MILLIS; 730 return (int) julianDay + EPOCH_JULIAN_DAY; 731 } 732 733 /** 734 * <p>Sets the time from the given Julian day number, which must be based on 735 * the same timezone that is set in this Time object. The "gmtoff" field 736 * need not be initialized because the given Julian day may have a different 737 * GMT offset than whatever is currently stored in this Time object anyway. 738 * After this method returns all the fields will be normalized and the time 739 * will be set to 12am at the beginning of the given Julian day. 740 * </p> 741 * 742 * <p> 743 * The only exception to this is if 12am does not exist for that day because 744 * of daylight saving time. For example, Cairo, Eqypt moves time ahead one 745 * hour at 12am on April 25, 2008 and there are a few other places that 746 * also change daylight saving time at 12am. In those cases, the time 747 * will be set to 1am. 748 * </p> 749 * 750 * @param julianDay the Julian day in the timezone for this Time object 751 * @return the UTC milliseconds for the beginning of the Julian day 752 */ setJulianDay(int julianDay)753 public long setJulianDay(int julianDay) { 754 // Don't bother with the GMT offset since we don't know the correct 755 // value for the given Julian day. Just get close and then adjust 756 // the day. 757 long millis = (julianDay - EPOCH_JULIAN_DAY) * DateUtils.DAY_IN_MILLIS; 758 set(millis); 759 760 // Figure out how close we are to the requested Julian day. 761 // We can't be off by more than a day. 762 int approximateDay = getJulianDay(millis, gmtoff); 763 int diff = julianDay - approximateDay; 764 monthDay += diff; 765 766 // Set the time to 12am and re-normalize. 767 hour = 0; 768 minute = 0; 769 second = 0; 770 millis = normalize(true); 771 return millis; 772 } 773 } 774