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 * An alternative to the {@link java.util.Calendar} and 26 * {@link 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 * 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. For example, 1970. 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 */ Time(String timezone)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 */ Time()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 */ Time(Time other)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 */ normalize(boolean ignoreDst)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 */ switchTimezone(String timezone)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 */ getActualMaximum(int field)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 */ clear(String timezone)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 * Compare two {@code Time} objects and return a negative number if {@code 285 * a} is less than {@code b}, a positive number if {@code a} is greater than 286 * {@code b}, or 0 if they are equal. 287 * 288 * @param a first {@code Time} instance to compare 289 * @param b second {@code Time} instance to compare 290 * @throws NullPointerException if either argument is {@code null} 291 * @throws IllegalArgumentException if {@link #allDay} is true but {@code 292 * hour}, {@code minute}, and {@code second} are not 0. 293 * @return a negative result if {@code a} is earlier, a positive result if 294 * {@code a} is earlier, or 0 if they are equal. 295 */ compare(Time a, Time b)296 public static int compare(Time a, Time b) { 297 if (a == null) { 298 throw new NullPointerException("a == null"); 299 } else if (b == null) { 300 throw new NullPointerException("b == null"); 301 } 302 303 return nativeCompare(a, b); 304 } 305 nativeCompare(Time a, Time b)306 private static native int nativeCompare(Time a, Time b); 307 308 /** 309 * Print the current value given the format string provided. See man 310 * strftime for what means what. The final string must be less than 256 311 * characters. 312 * @param format a string containing the desired format. 313 * @return a String containing the current time expressed in the current locale. 314 */ format(String format)315 public String format(String format) { 316 synchronized (Time.class) { 317 Locale locale = Locale.getDefault(); 318 319 if (sLocale == null || locale == null || !(locale.equals(sLocale))) { 320 Resources r = Resources.getSystem(); 321 322 sShortMonths = new String[] { 323 r.getString(com.android.internal.R.string.month_medium_january), 324 r.getString(com.android.internal.R.string.month_medium_february), 325 r.getString(com.android.internal.R.string.month_medium_march), 326 r.getString(com.android.internal.R.string.month_medium_april), 327 r.getString(com.android.internal.R.string.month_medium_may), 328 r.getString(com.android.internal.R.string.month_medium_june), 329 r.getString(com.android.internal.R.string.month_medium_july), 330 r.getString(com.android.internal.R.string.month_medium_august), 331 r.getString(com.android.internal.R.string.month_medium_september), 332 r.getString(com.android.internal.R.string.month_medium_october), 333 r.getString(com.android.internal.R.string.month_medium_november), 334 r.getString(com.android.internal.R.string.month_medium_december), 335 }; 336 sLongMonths = new String[] { 337 r.getString(com.android.internal.R.string.month_long_january), 338 r.getString(com.android.internal.R.string.month_long_february), 339 r.getString(com.android.internal.R.string.month_long_march), 340 r.getString(com.android.internal.R.string.month_long_april), 341 r.getString(com.android.internal.R.string.month_long_may), 342 r.getString(com.android.internal.R.string.month_long_june), 343 r.getString(com.android.internal.R.string.month_long_july), 344 r.getString(com.android.internal.R.string.month_long_august), 345 r.getString(com.android.internal.R.string.month_long_september), 346 r.getString(com.android.internal.R.string.month_long_october), 347 r.getString(com.android.internal.R.string.month_long_november), 348 r.getString(com.android.internal.R.string.month_long_december), 349 }; 350 sLongStandaloneMonths = new String[] { 351 r.getString(com.android.internal.R.string.month_long_standalone_january), 352 r.getString(com.android.internal.R.string.month_long_standalone_february), 353 r.getString(com.android.internal.R.string.month_long_standalone_march), 354 r.getString(com.android.internal.R.string.month_long_standalone_april), 355 r.getString(com.android.internal.R.string.month_long_standalone_may), 356 r.getString(com.android.internal.R.string.month_long_standalone_june), 357 r.getString(com.android.internal.R.string.month_long_standalone_july), 358 r.getString(com.android.internal.R.string.month_long_standalone_august), 359 r.getString(com.android.internal.R.string.month_long_standalone_september), 360 r.getString(com.android.internal.R.string.month_long_standalone_october), 361 r.getString(com.android.internal.R.string.month_long_standalone_november), 362 r.getString(com.android.internal.R.string.month_long_standalone_december), 363 }; 364 sShortWeekdays = new String[] { 365 r.getString(com.android.internal.R.string.day_of_week_medium_sunday), 366 r.getString(com.android.internal.R.string.day_of_week_medium_monday), 367 r.getString(com.android.internal.R.string.day_of_week_medium_tuesday), 368 r.getString(com.android.internal.R.string.day_of_week_medium_wednesday), 369 r.getString(com.android.internal.R.string.day_of_week_medium_thursday), 370 r.getString(com.android.internal.R.string.day_of_week_medium_friday), 371 r.getString(com.android.internal.R.string.day_of_week_medium_saturday), 372 }; 373 sLongWeekdays = new String[] { 374 r.getString(com.android.internal.R.string.day_of_week_long_sunday), 375 r.getString(com.android.internal.R.string.day_of_week_long_monday), 376 r.getString(com.android.internal.R.string.day_of_week_long_tuesday), 377 r.getString(com.android.internal.R.string.day_of_week_long_wednesday), 378 r.getString(com.android.internal.R.string.day_of_week_long_thursday), 379 r.getString(com.android.internal.R.string.day_of_week_long_friday), 380 r.getString(com.android.internal.R.string.day_of_week_long_saturday), 381 }; 382 sTimeOnlyFormat = r.getString(com.android.internal.R.string.time_of_day); 383 sDateOnlyFormat = r.getString(com.android.internal.R.string.month_day_year); 384 sDateTimeFormat = r.getString(com.android.internal.R.string.date_and_time); 385 sAm = r.getString(com.android.internal.R.string.am); 386 sPm = r.getString(com.android.internal.R.string.pm); 387 388 sLocale = locale; 389 } 390 391 return format1(format); 392 } 393 } 394 format1(String format)395 native private String format1(String format); 396 397 /** 398 * Return the current time in YYYYMMDDTHHMMSS<tz> format 399 */ 400 @Override toString()401 native public String toString(); 402 403 /** 404 * Parses a date-time string in either the RFC 2445 format or an abbreviated 405 * format that does not include the "time" field. For example, all of the 406 * following strings are valid: 407 * 408 * <ul> 409 * <li>"20081013T160000Z"</li> 410 * <li>"20081013T160000"</li> 411 * <li>"20081013"</li> 412 * </ul> 413 * 414 * Returns whether or not the time is in UTC (ends with Z). If the string 415 * ends with "Z" then the timezone is set to UTC. If the date-time string 416 * included only a date and no time field, then the <code>allDay</code> 417 * field of this Time class is set to true and the <code>hour</code>, 418 * <code>minute</code>, and <code>second</code> fields are set to zero; 419 * otherwise (a time field was included in the date-time string) 420 * <code>allDay</code> is set to false. The fields <code>weekDay</code>, 421 * <code>yearDay</code>, and <code>gmtoff</code> are always set to zero, 422 * and the field <code>isDst</code> is set to -1 (unknown). To set those 423 * fields, call {@link #normalize(boolean)} after parsing. 424 * 425 * To parse a date-time string and convert it to UTC milliseconds, do 426 * something like this: 427 * 428 * <pre> 429 * Time time = new Time(); 430 * String date = "20081013T160000Z"; 431 * time.parse(date); 432 * long millis = time.normalize(false); 433 * </pre> 434 * 435 * @param s the string to parse 436 * @return true if the resulting time value is in UTC time 437 * @throws android.util.TimeFormatException if s cannot be parsed. 438 */ parse(String s)439 public boolean parse(String s) { 440 if (nativeParse(s)) { 441 timezone = TIMEZONE_UTC; 442 return true; 443 } 444 return false; 445 } 446 447 /** 448 * Parse a time in the current zone in YYYYMMDDTHHMMSS format. 449 */ nativeParse(String s)450 native private boolean nativeParse(String s); 451 452 /** 453 * Parse a time in RFC 3339 format. This method also parses simple dates 454 * (that is, strings that contain no time or time offset). For example, 455 * all of the following strings are valid: 456 * 457 * <ul> 458 * <li>"2008-10-13T16:00:00.000Z"</li> 459 * <li>"2008-10-13T16:00:00.000+07:00"</li> 460 * <li>"2008-10-13T16:00:00.000-07:00"</li> 461 * <li>"2008-10-13"</li> 462 * </ul> 463 * 464 * <p> 465 * If the string contains a time and time offset, then the time offset will 466 * be used to convert the time value to UTC. 467 * </p> 468 * 469 * <p> 470 * If the given string contains just a date (with no time field), then 471 * the {@link #allDay} field is set to true and the {@link #hour}, 472 * {@link #minute}, and {@link #second} fields are set to zero. 473 * </p> 474 * 475 * <p> 476 * Returns true if the resulting time value is in UTC time. 477 * </p> 478 * 479 * @param s the string to parse 480 * @return true if the resulting time value is in UTC time 481 * @throws android.util.TimeFormatException if s cannot be parsed. 482 */ parse3339(String s)483 public boolean parse3339(String s) { 484 if (nativeParse3339(s)) { 485 timezone = TIMEZONE_UTC; 486 return true; 487 } 488 return false; 489 } 490 nativeParse3339(String s)491 native private boolean nativeParse3339(String s); 492 493 /** 494 * Returns the timezone string that is currently set for the device. 495 */ getCurrentTimezone()496 public static String getCurrentTimezone() { 497 return TimeZone.getDefault().getID(); 498 } 499 500 /** 501 * Sets the time of the given Time object to the current time. 502 */ setToNow()503 native public void setToNow(); 504 505 /** 506 * Converts this time to milliseconds. Suitable for interacting with the 507 * standard java libraries. The time is in UTC milliseconds since the epoch. 508 * This does an implicit normalization to compute the milliseconds but does 509 * <em>not</em> change any of the fields in this Time object. If you want 510 * to normalize the fields in this Time object and also get the milliseconds 511 * then use {@link #normalize(boolean)}. 512 * 513 * <p> 514 * If "ignoreDst" is false, then this method uses the current setting of the 515 * "isDst" field and will adjust the returned time if the "isDst" field is 516 * wrong for the given time. See the sample code below for an example of 517 * this. 518 * 519 * <p> 520 * If "ignoreDst" is true, then this method ignores the current setting of 521 * the "isDst" field in this Time object and will instead figure out the 522 * correct value of "isDst" (as best it can) from the fields in this 523 * Time object. The only case where this method cannot figure out the 524 * correct value of the "isDst" field is when the time is inherently 525 * ambiguous because it falls in the hour that is repeated when switching 526 * from Daylight-Saving Time to Standard Time. 527 * 528 * <p> 529 * Here is an example where <tt>toMillis(true)</tt> adjusts the time, 530 * assuming that DST changes at 2am on Sunday, Nov 4, 2007. 531 * 532 * <pre> 533 * Time time = new Time(); 534 * time.set(4, 10, 2007); // set the date to Nov 4, 2007, 12am 535 * time.normalize(); // this sets isDst = 1 536 * time.monthDay += 1; // changes the date to Nov 5, 2007, 12am 537 * millis = time.toMillis(false); // millis is Nov 4, 2007, 11pm 538 * millis = time.toMillis(true); // millis is Nov 5, 2007, 12am 539 * </pre> 540 * 541 * <p> 542 * To avoid this problem, use <tt>toMillis(true)</tt> 543 * after adding or subtracting days or explicitly setting the "monthDay" 544 * field. On the other hand, if you are adding 545 * or subtracting hours or minutes, then you should use 546 * <tt>toMillis(false)</tt>. 547 * 548 * <p> 549 * You should also use <tt>toMillis(false)</tt> if you want 550 * to read back the same milliseconds that you set with {@link #set(long)} 551 * or {@link #set(Time)} or after parsing a date string. 552 */ toMillis(boolean ignoreDst)553 native public long toMillis(boolean ignoreDst); 554 555 /** 556 * Sets the fields in this Time object given the UTC milliseconds. After 557 * this method returns, all the fields are normalized. 558 * This also sets the "isDst" field to the correct value. 559 * 560 * @param millis the time in UTC milliseconds since the epoch. 561 */ set(long millis)562 native public void set(long millis); 563 564 /** 565 * Format according to RFC 2445 DATETIME type. 566 * 567 * <p> 568 * The same as format("%Y%m%dT%H%M%S"). 569 */ format2445()570 native public String format2445(); 571 572 /** 573 * Copy the value of that to this Time object. No normalization happens. 574 */ set(Time that)575 public void set(Time that) { 576 this.timezone = that.timezone; 577 this.allDay = that.allDay; 578 this.second = that.second; 579 this.minute = that.minute; 580 this.hour = that.hour; 581 this.monthDay = that.monthDay; 582 this.month = that.month; 583 this.year = that.year; 584 this.weekDay = that.weekDay; 585 this.yearDay = that.yearDay; 586 this.isDst = that.isDst; 587 this.gmtoff = that.gmtoff; 588 } 589 590 /** 591 * Sets the fields. Sets weekDay, yearDay and gmtoff to 0, and isDst to -1. 592 * Call {@link #normalize(boolean)} if you need those. 593 */ set(int second, int minute, int hour, int monthDay, int month, int year)594 public void set(int second, int minute, int hour, int monthDay, int month, int year) { 595 this.allDay = false; 596 this.second = second; 597 this.minute = minute; 598 this.hour = hour; 599 this.monthDay = monthDay; 600 this.month = month; 601 this.year = year; 602 this.weekDay = 0; 603 this.yearDay = 0; 604 this.isDst = -1; 605 this.gmtoff = 0; 606 } 607 608 /** 609 * Sets the date from the given fields. Also sets allDay to true. 610 * Sets weekDay, yearDay and gmtoff to 0, and isDst to -1. 611 * Call {@link #normalize(boolean)} if you need those. 612 * 613 * @param monthDay the day of the month (in the range [1,31]) 614 * @param month the zero-based month number (in the range [0,11]) 615 * @param year the year 616 */ set(int monthDay, int month, int year)617 public void set(int monthDay, int month, int year) { 618 this.allDay = true; 619 this.second = 0; 620 this.minute = 0; 621 this.hour = 0; 622 this.monthDay = monthDay; 623 this.month = month; 624 this.year = year; 625 this.weekDay = 0; 626 this.yearDay = 0; 627 this.isDst = -1; 628 this.gmtoff = 0; 629 } 630 631 /** 632 * Returns true if the time represented by this Time object occurs before 633 * the given time. 634 * 635 * @param that a given Time object to compare against 636 * @return true if this time is less than the given time 637 */ before(Time that)638 public boolean before(Time that) { 639 return Time.compare(this, that) < 0; 640 } 641 642 643 /** 644 * Returns true if the time represented by this Time object occurs after 645 * the given time. 646 * 647 * @param that a given Time object to compare against 648 * @return true if this time is greater than the given time 649 */ after(Time that)650 public boolean after(Time that) { 651 return Time.compare(this, that) > 0; 652 } 653 654 /** 655 * This array is indexed by the weekDay field (SUNDAY=0, MONDAY=1, etc.) 656 * and gives a number that can be added to the yearDay to give the 657 * closest Thursday yearDay. 658 */ 659 private static final int[] sThursdayOffset = { -3, 3, 2, 1, 0, -1, -2 }; 660 661 /** 662 * Computes the week number according to ISO 8601. The current Time 663 * object must already be normalized because this method uses the 664 * yearDay and weekDay fields. 665 * 666 * <p> 667 * In IS0 8601, weeks start on Monday. 668 * The first week of the year (week 1) is defined by ISO 8601 as the 669 * first week with four or more of its days in the starting year. 670 * Or equivalently, the week containing January 4. Or equivalently, 671 * the week with the year's first Thursday in it. 672 * </p> 673 * 674 * <p> 675 * The week number can be calculated by counting Thursdays. Week N 676 * contains the Nth Thursday of the year. 677 * </p> 678 * 679 * @return the ISO week number. 680 */ getWeekNumber()681 public int getWeekNumber() { 682 // Get the year day for the closest Thursday 683 int closestThursday = yearDay + sThursdayOffset[weekDay]; 684 685 // Year days start at 0 686 if (closestThursday >= 0 && closestThursday <= 364) { 687 return closestThursday / 7 + 1; 688 } 689 690 // The week crosses a year boundary. 691 Time temp = new Time(this); 692 temp.monthDay += sThursdayOffset[weekDay]; 693 temp.normalize(true /* ignore isDst */); 694 return temp.yearDay / 7 + 1; 695 } 696 697 /** 698 * Return a string in the RFC 3339 format. 699 * <p> 700 * If allDay is true, expresses the time as Y-M-D</p> 701 * <p> 702 * Otherwise, if the timezone is UTC, expresses the time as Y-M-D-T-H-M-S UTC</p> 703 * <p> 704 * Otherwise the time is expressed the time as Y-M-D-T-H-M-S +- GMT</p> 705 * @param allDay 706 * @return string in the RFC 3339 format. 707 */ format3339(boolean allDay)708 public String format3339(boolean allDay) { 709 if (allDay) { 710 return format(Y_M_D); 711 } else if (TIMEZONE_UTC.equals(timezone)) { 712 return format(Y_M_D_T_H_M_S_000_Z); 713 } else { 714 String base = format(Y_M_D_T_H_M_S_000); 715 String sign = (gmtoff < 0) ? "-" : "+"; 716 int offset = (int)Math.abs(gmtoff); 717 int minutes = (offset % 3600) / 60; 718 int hours = offset / 3600; 719 720 return String.format("%s%s%02d:%02d", base, sign, hours, minutes); 721 } 722 } 723 724 /** 725 * Returns true if the day of the given time is the epoch on the Julian Calendar 726 * (January 1, 1970 on the Gregorian calendar). 727 * 728 * @param time the time to test 729 * @return true if epoch. 730 */ isEpoch(Time time)731 public static boolean isEpoch(Time time) { 732 long millis = time.toMillis(true); 733 return getJulianDay(millis, 0) == EPOCH_JULIAN_DAY; 734 } 735 736 /** 737 * Computes the Julian day number, given the UTC milliseconds 738 * and the offset (in seconds) from UTC. The Julian day for a given 739 * date will be the same for every timezone. For example, the Julian 740 * day for July 1, 2008 is 2454649. This is the same value no matter 741 * what timezone is being used. The Julian day is useful for testing 742 * if two events occur on the same day and for determining the relative 743 * time of an event from the present ("yesterday", "3 days ago", etc.). 744 * 745 * <p> 746 * Use {@link #toMillis(boolean)} to get the milliseconds. 747 * 748 * @param millis the time in UTC milliseconds 749 * @param gmtoff the offset from UTC in seconds 750 * @return the Julian day 751 */ getJulianDay(long millis, long gmtoff)752 public static int getJulianDay(long millis, long gmtoff) { 753 long offsetMillis = gmtoff * 1000; 754 long julianDay = (millis + offsetMillis) / DateUtils.DAY_IN_MILLIS; 755 return (int) julianDay + EPOCH_JULIAN_DAY; 756 } 757 758 /** 759 * <p>Sets the time from the given Julian day number, which must be based on 760 * the same timezone that is set in this Time object. The "gmtoff" field 761 * need not be initialized because the given Julian day may have a different 762 * GMT offset than whatever is currently stored in this Time object anyway. 763 * After this method returns all the fields will be normalized and the time 764 * will be set to 12am at the beginning of the given Julian day. 765 * </p> 766 * 767 * <p> 768 * The only exception to this is if 12am does not exist for that day because 769 * of daylight saving time. For example, Cairo, Eqypt moves time ahead one 770 * hour at 12am on April 25, 2008 and there are a few other places that 771 * also change daylight saving time at 12am. In those cases, the time 772 * will be set to 1am. 773 * </p> 774 * 775 * @param julianDay the Julian day in the timezone for this Time object 776 * @return the UTC milliseconds for the beginning of the Julian day 777 */ setJulianDay(int julianDay)778 public long setJulianDay(int julianDay) { 779 // Don't bother with the GMT offset since we don't know the correct 780 // value for the given Julian day. Just get close and then adjust 781 // the day. 782 long millis = (julianDay - EPOCH_JULIAN_DAY) * DateUtils.DAY_IN_MILLIS; 783 set(millis); 784 785 // Figure out how close we are to the requested Julian day. 786 // We can't be off by more than a day. 787 int approximateDay = getJulianDay(millis, gmtoff); 788 int diff = julianDay - approximateDay; 789 monthDay += diff; 790 791 // Set the time to 12am and re-normalize. 792 hour = 0; 793 minute = 0; 794 second = 0; 795 millis = normalize(true); 796 return millis; 797 } 798 799 /** 800 * Returns the week since {@link #EPOCH_JULIAN_DAY} (Jan 1, 1970) adjusted 801 * for first day of week. This takes a julian day and the week start day and 802 * calculates which week since {@link #EPOCH_JULIAN_DAY} that day occurs in, 803 * starting at 0. *Do not* use this to compute the ISO week number for the 804 * year. 805 * 806 * @param julianDay The julian day to calculate the week number for 807 * @param firstDayOfWeek Which week day is the first day of the week, see 808 * {@link #SUNDAY} 809 * @return Weeks since the epoch 810 */ getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek)811 public static int getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek) { 812 int diff = THURSDAY - firstDayOfWeek; 813 if (diff < 0) { 814 diff += 7; 815 } 816 int refDay = EPOCH_JULIAN_DAY - diff; 817 return (julianDay - refDay) / 7; 818 } 819 820 /** 821 * Takes a number of weeks since the epoch and calculates the Julian day of 822 * the Monday for that week. This assumes that the week containing the 823 * {@link #EPOCH_JULIAN_DAY} is considered week 0. It returns the Julian day 824 * for the Monday week weeks after the Monday of the week containing the 825 * epoch. 826 * 827 * @param week Number of weeks since the epoch 828 * @return The julian day for the Monday of the given week since the epoch 829 */ getJulianMondayFromWeeksSinceEpoch(int week)830 public static int getJulianMondayFromWeeksSinceEpoch(int week) { 831 return MONDAY_BEFORE_JULIAN_EPOCH + week * 7; 832 } 833 } 834