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