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