MonthWeekEventsView.java revision 09b1b2e1e7eb9bc291d4e70c993471d3c9ffa799
1/* 2 * Copyright (C) 2010 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 com.android.calendar.month; 18 19import com.android.calendar.Event; 20import com.android.calendar.R; 21import com.android.calendar.Utils; 22 23import android.content.Context; 24import android.content.res.Configuration; 25import android.content.res.Resources; 26import android.graphics.Canvas; 27import android.graphics.Paint; 28import android.graphics.Paint.Align; 29import android.graphics.Paint.Style; 30import android.graphics.Typeface; 31import android.graphics.drawable.Drawable; 32import android.text.TextPaint; 33import android.text.TextUtils; 34import android.text.format.DateUtils; 35import android.text.format.Time; 36import android.util.Log; 37 38import java.util.ArrayList; 39import java.util.Arrays; 40import java.util.Formatter; 41import java.util.HashMap; 42import java.util.Iterator; 43import java.util.List; 44import java.util.Locale; 45 46public class MonthWeekEventsView extends SimpleWeekView { 47 private static final String TAG = "MonthView"; 48 49 public static final String VIEW_PARAMS_ORIENTATION = "orientation"; 50 51 private static int TEXT_SIZE_MONTH_NUMBER = 32; 52 private static int TEXT_SIZE_EVENT = 14; 53 private static int TEXT_SIZE_MORE_EVENTS = 12; 54 private static int TEXT_SIZE_MONTH_NAME = 14; 55 private static int TEXT_SIZE_WEEK_NUM = 12; 56 57 private static final int DEFAULT_EDGE_SPACING = 4; 58 private static int PADDING_MONTH_NUMBER = 4; 59 private static int PADDING_WEEK_NUMBER = 16; 60 private static int DAY_SEPARATOR_OUTER_WIDTH = 5; 61 private static int DAY_SEPARATOR_INNER_WIDTH = 1; 62 private static int DAY_SEPARATOR_VERTICAL_LENGTH = 53; 63 private static int DAY_SEPARATOR_VERTICAL_LENGHT_PORTRAIT = 64; 64 65 private static int EVENT_X_OFFSET_LANDSCAPE = 44; 66 private static int EVENT_Y_OFFSET_LANDSCAPE = 11; 67 private static int EVENT_Y_OFFSET_PORTRAIT = 18; 68 private static int EVENT_SQUARE_WIDTH = 10; 69 private static int EVENT_SQUARE_BORDER = 1; 70 private static int EVENT_LINE_PADDING = 4; 71 private static int EVENT_RIGHT_PADDING = 4; 72 private static int EVENT_BOTTOM_PADDING = 15; 73 74 private static int SPACING_WEEK_NUMBER = 19; 75 private static boolean mScaled = false; 76 77 protected Time mToday = new Time(); 78 protected boolean mHasToday = false; 79 protected int mTodayIndex = -1; 80 protected int mOrientation = Configuration.ORIENTATION_LANDSCAPE; 81 protected List<ArrayList<Event>> mEvents = null; 82 // This is for drawing the outlines around event chips and supports up to 10 83 // events being drawn on each day. The code will expand this if necessary. 84 protected FloatRef mEventOutlines = new FloatRef(10 * 4 * 4 * 7); 85 86 protected static StringBuilder mStringBuilder = new StringBuilder(50); 87 // TODO recreate formatter when locale changes 88 protected static Formatter mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); 89 90 protected Paint mMonthNamePaint; 91 protected TextPaint mEventPaint; 92 protected TextPaint mEventExtrasPaint; 93 protected Paint mWeekNumPaint; 94 95 protected Drawable mTodayDrawable; 96 97 protected int mMonthNumHeight; 98 protected int mEventHeight; 99 protected int mExtrasHeight; 100 protected int mWeekNumHeight; 101 102 protected int mMonthNumColor; 103 protected int mMonthNumOtherColor; 104 protected int mMonthNumTodayColor; 105 protected int mMonthNameColor; 106 protected int mMonthNameOtherColor; 107 protected int mMonthEventColor; 108 protected int mMonthEventExtraColor; 109 protected int mMonthEventOtherColor; 110 protected int mMonthEventExtraOtherColor; 111 protected int mMonthWeekNumColor; 112 113 protected int mEventChipOutlineColor = 0xFFFFFFFF; 114 protected int mDaySeparatorOuterColor = 0x33FFFFFF; 115 protected int mDaySeparatorInnerColor = 0x1A000000; 116 117 /** 118 * This provides a reference to a float array which allows for easy size 119 * checking and reallocation. Used for drawing lines. 120 */ 121 private class FloatRef { 122 float[] array; 123 124 public FloatRef(int size) { 125 array = new float[size]; 126 } 127 128 public void ensureSize(int newSize) { 129 if (newSize >= array.length) { 130 // Add enough space for 7 more boxes to be drawn 131 array = Arrays.copyOf(array, newSize + 16 * 7); 132 } 133 } 134 } 135 136 /** 137 * @param context 138 */ 139 public MonthWeekEventsView(Context context) { 140 super(context); 141 142 Resources resources = context.getResources(); 143 TEXT_SIZE_MONTH_NUMBER = resources.getInteger(R.integer.text_size_month_number); 144 145 mPadding = DEFAULT_EDGE_SPACING; 146 if (mScale != 1 && !mScaled) { 147 PADDING_MONTH_NUMBER *= mScale; 148 PADDING_WEEK_NUMBER *= mScale; 149 SPACING_WEEK_NUMBER *= mScale; 150 TEXT_SIZE_MONTH_NUMBER *= mScale; 151 TEXT_SIZE_EVENT *= mScale; 152 TEXT_SIZE_MORE_EVENTS *= mScale; 153 TEXT_SIZE_MONTH_NAME *= mScale; 154 TEXT_SIZE_WEEK_NUM *= mScale; 155 DAY_SEPARATOR_OUTER_WIDTH *= mScale; 156 DAY_SEPARATOR_INNER_WIDTH *= mScale; 157 DAY_SEPARATOR_VERTICAL_LENGTH *= mScale; 158 DAY_SEPARATOR_VERTICAL_LENGHT_PORTRAIT *= mScale; 159 EVENT_X_OFFSET_LANDSCAPE *= mScale; 160 EVENT_Y_OFFSET_LANDSCAPE *= mScale; 161 EVENT_Y_OFFSET_PORTRAIT *= mScale; 162 EVENT_SQUARE_WIDTH *= mScale; 163 EVENT_LINE_PADDING *= mScale; 164 EVENT_BOTTOM_PADDING *= mScale; 165 EVENT_RIGHT_PADDING *= mScale; 166 mPadding = (int) (DEFAULT_EDGE_SPACING * mScale); 167 mScaled = true; 168 } 169 } 170 171 public void setEvents(List<ArrayList<Event>> sortedEvents) { 172 mEvents = sortedEvents; 173 if (sortedEvents == null) { 174 return; 175 } 176 if (sortedEvents.size() != mNumDays) { 177 if (Log.isLoggable(TAG, Log.ERROR)) { 178 Log.wtf(TAG, "Events size must be same as days displayed: size=" 179 + sortedEvents.size() + " days=" + mNumDays); 180 } 181 mEvents = null; 182 return; 183 } 184 } 185 186 protected void loadColors(Context context) { 187 Resources res = context.getResources(); 188 mMonthWeekNumColor = res.getColor(R.color.month_week_num_color); 189 mMonthNumColor = res.getColor(R.color.month_day_number); 190 mMonthNumOtherColor = res.getColor(R.color.month_day_number_other); 191 mMonthNumTodayColor = res.getColor(R.color.month_today_number); 192 mMonthNameColor = mMonthNumColor; 193 mMonthNameOtherColor = mMonthNumOtherColor; 194 mMonthEventColor = res.getColor(R.color.month_event_color); 195 mMonthEventExtraColor = res.getColor(R.color.month_event_extra_color); 196 mMonthEventOtherColor = res.getColor(R.color.month_event_other_color); 197 mMonthEventExtraOtherColor = res.getColor(R.color.month_event_extra_other_color); 198 199 mTodayDrawable = res.getDrawable(R.drawable.today_blue_week_holo_light); 200 } 201 202 /** 203 * Sets up the text and style properties for painting. Override this if you 204 * want to use a different paint. 205 */ 206 @Override 207 protected void setPaintProperties() { 208 loadColors(mContext); 209 // TODO modify paint properties depending on isMini 210 p.setStyle(Style.FILL); 211 212 mMonthNumPaint = new Paint(); 213 mMonthNumPaint.setFakeBoldText(false); 214 mMonthNumPaint.setAntiAlias(true); 215 mMonthNumPaint.setTextSize(TEXT_SIZE_MONTH_NUMBER); 216 mMonthNumPaint.setColor(mMonthNumColor); 217 mMonthNumPaint.setStyle(Style.FILL); 218 mMonthNumPaint.setTextAlign(Align.LEFT); 219 mMonthNumPaint.setTypeface(Typeface.DEFAULT_BOLD); 220 221 mMonthNumHeight = (int) (-mMonthNumPaint.ascent()); 222 223 mEventPaint = new TextPaint(); 224 mEventPaint.setFakeBoldText(false); 225 mEventPaint.setAntiAlias(true); 226 mEventPaint.setTextSize(TEXT_SIZE_EVENT); 227 mEventPaint.setColor(mMonthEventColor); 228 229 mEventHeight = (int) (-mEventPaint.ascent()); 230 231 mEventExtrasPaint = new TextPaint(); 232 mEventExtrasPaint.setFakeBoldText(false); 233 mEventExtrasPaint.setAntiAlias(true); 234 mEventExtrasPaint.setStrokeWidth(EVENT_SQUARE_BORDER); 235 mEventExtrasPaint.setTextSize(TEXT_SIZE_EVENT); 236 mEventExtrasPaint.setColor(mMonthEventExtraColor); 237 mEventExtrasPaint.setStyle(Style.FILL); 238 mEventExtrasPaint.setTextAlign(Align.LEFT); 239 240 mWeekNumPaint = new Paint(); 241 mWeekNumPaint.setFakeBoldText(false); 242 mWeekNumPaint.setAntiAlias(true); 243 mWeekNumPaint.setTextSize(TEXT_SIZE_WEEK_NUM); 244 mWeekNumPaint.setColor(mWeekNumColor); 245 mWeekNumPaint.setStyle(Style.FILL); 246 mWeekNumPaint.setTextAlign(Align.RIGHT); 247 248 mWeekNumHeight = (int) (-mWeekNumPaint.ascent()); 249 } 250 251 @Override 252 public void setWeekParams(HashMap<String, Integer> params, String tz) { 253 super.setWeekParams(params, tz); 254 255 if (params.containsKey(VIEW_PARAMS_ORIENTATION)) { 256 mOrientation = params.get(VIEW_PARAMS_ORIENTATION); 257 } 258 259 mToday.timezone = tz; 260 mToday.setToNow(); 261 mToday.normalize(true); 262 int julianToday = Time.getJulianDay(mToday.toMillis(false), mToday.gmtoff); 263 if (julianToday >= mFirstJulianDay && julianToday < mFirstJulianDay + mNumDays) { 264 mHasToday = true; 265 mTodayIndex = julianToday - mFirstJulianDay; 266 } else { 267 mHasToday = false; 268 mTodayIndex = -1; 269 } 270 mNumCells = mNumDays + 1; 271 } 272 273 @Override 274 protected void onDraw(Canvas canvas) { 275 drawBackground(canvas); 276 drawWeekNums(canvas); 277 drawDaySeparators(canvas); 278 drawEvents(canvas); 279 } 280 281 @Override 282 protected void drawDaySeparators(Canvas canvas) { 283 // mDaySeparatorOuterColor 284 float lines[] = new float[8 * 4]; 285 int count = 7 * 4; 286 int wkNumOffset = 0; 287 int effectiveWidth = mWidth - mPadding * 2; 288 count += 4; 289 wkNumOffset = 1; 290 effectiveWidth -= SPACING_WEEK_NUMBER; 291 lines[0] = mPadding; 292 lines[1] = DAY_SEPARATOR_OUTER_WIDTH / 2 + 1; 293 lines[2] = mWidth - mPadding; 294 lines[3] = lines[1]; 295 int y0 = DAY_SEPARATOR_OUTER_WIDTH / 2 + DAY_SEPARATOR_INNER_WIDTH; 296 int y1; 297 if (mOrientation == Configuration.ORIENTATION_PORTRAIT) { 298 y1 = y0 + DAY_SEPARATOR_VERTICAL_LENGHT_PORTRAIT; 299 } else { 300 y1 = y0 + DAY_SEPARATOR_VERTICAL_LENGTH; 301 } 302 303 for (int i = 4; i < count;) { 304 int x = (i / 4 - wkNumOffset) * effectiveWidth / (mNumDays) + mPadding 305 + (SPACING_WEEK_NUMBER * wkNumOffset); 306 lines[i++] = x; 307 lines[i++] = y0; 308 lines[i++] = x; 309 lines[i++] = y1; 310 } 311 p.setColor(mDaySeparatorOuterColor); 312 p.setStrokeWidth(DAY_SEPARATOR_OUTER_WIDTH); 313 canvas.drawLines(lines, 0, count, p); 314 p.setColor(mDaySeparatorInnerColor); 315 p.setStrokeWidth(DAY_SEPARATOR_INNER_WIDTH); 316 canvas.drawLines(lines, 0, count, p); 317 } 318 319 @Override 320 protected void drawBackground(Canvas canvas) { 321 if (mHasToday) { 322 p.setColor(mSelectedWeekBGColor); 323 } else { 324 return; 325 } 326 int wkNumOffset = 0; 327 int effectiveWidth = mWidth - mPadding * 2; 328 wkNumOffset = 1; 329 effectiveWidth -= SPACING_WEEK_NUMBER; 330 r.top = DAY_SEPARATOR_OUTER_WIDTH + 1; 331 r.bottom = mHeight; 332 r.left = (mTodayIndex) * effectiveWidth / (mNumDays) + mPadding 333 + (SPACING_WEEK_NUMBER * wkNumOffset) + DAY_SEPARATOR_OUTER_WIDTH / 2 + 1; 334 r.right = (mTodayIndex + 1) * effectiveWidth / (mNumDays) + mPadding 335 + (SPACING_WEEK_NUMBER * wkNumOffset) - DAY_SEPARATOR_OUTER_WIDTH / 2; 336 mTodayDrawable.setBounds(r); 337 mTodayDrawable.draw(canvas); 338 } 339 340 @Override 341 protected void drawWeekNums(Canvas canvas) { 342 int y; 343 344 int i = 0; 345 int offset = 0; 346 int effectiveWidth = mWidth - mPadding * 2; 347 int todayIndex = mTodayIndex; 348 int x = PADDING_WEEK_NUMBER + mPadding; 349 int numCount = mNumDays; 350 y = mWeekNumHeight + PADDING_MONTH_NUMBER; 351 if (mShowWeekNum) { 352 canvas.drawText(mDayNumbers[0], x, y, mWeekNumPaint); 353 numCount++; 354 i++; 355 todayIndex++; 356 offset++; 357 } 358 effectiveWidth -= SPACING_WEEK_NUMBER; 359 360 y = (mMonthNumHeight + PADDING_MONTH_NUMBER); 361 362 boolean isFocusMonth = mFocusDay[i]; 363 mMonthNumPaint.setColor(isFocusMonth ? mMonthNumColor : mMonthNumOtherColor); 364 for (; i < numCount; i++) { 365 if (mHasToday && todayIndex == i) { 366 mMonthNumPaint.setColor(mMonthNumTodayColor); 367 if (i + 1 < numCount) { 368 // Make sure the color will be set back on the next 369 // iteration 370 isFocusMonth = !mFocusDay[i + 1]; 371 } 372 } else if (mFocusDay[i] != isFocusMonth) { 373 isFocusMonth = mFocusDay[i]; 374 mMonthNumPaint.setColor(isFocusMonth ? mMonthNumColor : mMonthNumOtherColor); 375 } 376 x = (i - offset) * effectiveWidth / (mNumDays) + mPadding + PADDING_MONTH_NUMBER 377 + SPACING_WEEK_NUMBER; 378 canvas.drawText(mDayNumbers[i], x, y, mMonthNumPaint); 379 380 } 381 } 382 383 protected void drawEvents(Canvas canvas) { 384 if (mEvents == null) { 385 return; 386 } 387 int wkNumOffset = 0; 388 int effectiveWidth = mWidth - mPadding * 2; 389 wkNumOffset = 1; 390 effectiveWidth -= SPACING_WEEK_NUMBER; 391 392 int day = -1; 393 int outlineCount = 0; 394 for (ArrayList<Event> eventDay : mEvents) { 395 day++; 396 if (eventDay == null || eventDay.size() == 0) { 397 continue; 398 } 399 int ySquare; 400 int xSquare = day * effectiveWidth / (mNumDays) + mPadding 401 + (SPACING_WEEK_NUMBER * wkNumOffset); 402 if (mOrientation == Configuration.ORIENTATION_PORTRAIT) { 403 ySquare = EVENT_Y_OFFSET_PORTRAIT + mMonthNumHeight + PADDING_MONTH_NUMBER; 404 xSquare += PADDING_MONTH_NUMBER + 1; 405 } else { 406 ySquare = EVENT_Y_OFFSET_LANDSCAPE; 407 xSquare += EVENT_X_OFFSET_LANDSCAPE; 408 } 409 int rightEdge = (day + 1) * effectiveWidth / (mNumDays) + mPadding 410 + (SPACING_WEEK_NUMBER * wkNumOffset) - EVENT_RIGHT_PADDING; 411 int eventCount = 0; 412 Iterator<Event> iter = eventDay.iterator(); 413 while (iter.hasNext()) { 414 Event event = iter.next(); 415 int newY = drawEvent(canvas, event, xSquare, ySquare, rightEdge, iter.hasNext()); 416 if (newY == ySquare) { 417 break; 418 } 419 outlineCount = addChipOutline(mEventOutlines, outlineCount, xSquare, ySquare); 420 eventCount++; 421 ySquare = newY; 422 } 423 424 int remaining = eventDay.size() - eventCount; 425 if (remaining > 0) { 426 drawMoreEvents(canvas, remaining, xSquare); 427 } 428 } 429 if (outlineCount > 0) { 430 p.setColor(mEventChipOutlineColor); 431 p.setStrokeWidth(EVENT_SQUARE_BORDER); 432 canvas.drawLines(mEventOutlines.array, 0, outlineCount, p); 433 } 434 } 435 436 protected int addChipOutline(FloatRef lines, int count, int x, int y) { 437 lines.ensureSize(count + 16); 438 // top of box 439 lines.array[count++] = x; 440 lines.array[count++] = y; 441 lines.array[count++] = x + EVENT_SQUARE_WIDTH; 442 lines.array[count++] = y; 443 // right side of box 444 lines.array[count++] = x + EVENT_SQUARE_WIDTH; 445 lines.array[count++] = y; 446 lines.array[count++] = x + EVENT_SQUARE_WIDTH; 447 lines.array[count++] = y + EVENT_SQUARE_WIDTH; 448 // left side of box 449 lines.array[count++] = x; 450 lines.array[count++] = y; 451 lines.array[count++] = x; 452 lines.array[count++] = y + EVENT_SQUARE_WIDTH + 1; 453 // bottom of box 454 lines.array[count++] = x; 455 lines.array[count++] = y + EVENT_SQUARE_WIDTH; 456 lines.array[count++] = x + EVENT_SQUARE_WIDTH + 1; 457 lines.array[count++] = y + EVENT_SQUARE_WIDTH; 458 459 return count; 460 } 461 462 /** 463 * Attempts to draw the given event. Returns the y for the next event or the 464 * original y if the event will not fit. An event is considered to not fit 465 * if the event and its extras won't fit or if there are more events and the 466 * more events line would not fit after drawing this event. 467 * 468 * @param event the event to draw 469 * @param x the top left corner for this event's color chip 470 * @param y the top left corner for this event's color chip 471 * @return the y for the next event or the original y if it won't fit 472 */ 473 protected int drawEvent( 474 Canvas canvas, Event event, int x, int y, int rightEdge, boolean moreEvents) { 475 int requiredSpace = EVENT_LINE_PADDING + mEventHeight; 476 int multiplier = 1; 477 if (moreEvents) { 478 multiplier++; 479 } 480 if (!event.allDay) { 481 multiplier++; 482 } 483 requiredSpace *= multiplier; 484 if (requiredSpace + y >= mHeight - EVENT_BOTTOM_PADDING) { 485 // Not enough space, return 486 return y; 487 } 488 r.left = x; 489 r.right = x + EVENT_SQUARE_WIDTH; 490 r.top = y; 491 r.bottom = y + EVENT_SQUARE_WIDTH; 492 p.setColor(event.color); 493 canvas.drawRect(r, p); 494 495 int textX = x + EVENT_SQUARE_WIDTH + EVENT_LINE_PADDING; 496 int textY = y + mEventHeight - EVENT_LINE_PADDING / 2; 497 float avail = rightEdge - textX; 498 CharSequence text = TextUtils.ellipsize( 499 event.title, mEventPaint, avail, TextUtils.TruncateAt.END); 500 canvas.drawText(text.toString(), textX, textY, mEventPaint); 501 if (!event.allDay) { 502 textY += mEventHeight + EVENT_LINE_PADDING; 503 mStringBuilder.setLength(0); 504 text = DateUtils.formatDateRange(mContext, mFormatter, event.startMillis, 505 event.endMillis, DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, 506 Utils.getTimeZone(mContext, null)).toString(); 507 text = TextUtils.ellipsize(text, mEventExtrasPaint, avail, TextUtils.TruncateAt.END); 508 canvas.drawText(text.toString(), textX, textY, mEventExtrasPaint); 509 } 510 511 return textY + EVENT_LINE_PADDING; 512 } 513 514 protected void drawMoreEvents(Canvas canvas, int remainingEvents, int x) { 515 FloatRef lines = new FloatRef(4 * 4); 516 int y = mHeight - EVENT_BOTTOM_PADDING + EVENT_LINE_PADDING / 2 - mEventHeight; 517 addChipOutline(lines, 0, x, y); 518 canvas.drawLines(lines.array, mEventExtrasPaint); 519 String text = mContext.getResources().getQuantityString( 520 R.plurals.month_more_events, remainingEvents); 521 y = mHeight - EVENT_BOTTOM_PADDING; 522 x += EVENT_SQUARE_WIDTH + EVENT_LINE_PADDING; 523 mEventExtrasPaint.setFakeBoldText(true); 524 canvas.drawText(String.format(text, remainingEvents), x, y, mEventExtrasPaint); 525 mEventExtrasPaint.setFakeBoldText(false); 526 } 527 528 @Override 529 protected void updateSelectionPositions() { 530 if (mHasSelectedDay) { 531 int selectedPosition = mSelectedDay - mWeekStart; 532 if (selectedPosition < 0) { 533 selectedPosition += 7; 534 } 535 int effectiveWidth = mWidth - mPadding * 2; 536 effectiveWidth -= SPACING_WEEK_NUMBER; 537 mSelectedLeft = selectedPosition * effectiveWidth / mNumDays + mPadding; 538 mSelectedRight = (selectedPosition + 1) * effectiveWidth / mNumDays + mPadding; 539 mSelectedLeft += SPACING_WEEK_NUMBER; 540 mSelectedRight += SPACING_WEEK_NUMBER; 541 } 542 } 543 544 @Override 545 public Time getDayFromLocation(float x) { 546 int dayStart = SPACING_WEEK_NUMBER + mPadding; 547 if (x < dayStart || x > mWidth - mPadding) { 548 return null; 549 } 550 // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels 551 int dayPosition = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding)); 552 int day = mFirstJulianDay + dayPosition; 553 554 Time time = new Time(mTimeZone); 555 if (mWeek == 0) { 556 // This week is weird... 557 if (day < Time.EPOCH_JULIAN_DAY) { 558 day++; 559 } else if (day == Time.EPOCH_JULIAN_DAY) { 560 time.set(1, 0, 1970); 561 time.normalize(true); 562 return time; 563 } 564 } 565 566 time.setJulianDay(day); 567 return time; 568 } 569 570} 571