net.karlmartens.ui.widget.Calendar.java Source code

Java tutorial

Introduction

Here is the source code for net.karlmartens.ui.widget.Calendar.java

Source

/**
 *   Copyright 2012 Karl Martens
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *       
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 *   net.karlmartens.ui, is a library of UI widgets
 */
package net.karlmartens.ui.widget;

import static java.util.Calendar.DAY_OF_MONTH;
import static java.util.Calendar.DAY_OF_WEEK;
import static java.util.Calendar.MONTH;
import static java.util.Calendar.YEAR;
import static net.karlmartens.platform.util.DateSupport.toLocalDate;
import static org.eclipse.swt.SWT.ERROR_INVALID_ARGUMENT;
import static org.eclipse.swt.SWT.ERROR_INVALID_RANGE;
import static org.eclipse.swt.SWT.error;

import java.text.DateFormatSymbols;
import java.util.Arrays;
import java.util.Locale;

import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;

/**
 * @author karl
 * 
 */
public final class Calendar extends Composite {

    private static final int ROW_COUNT = 7;
    private static final YearMonthDay _NONE = new YearMonthDay(-1, -1, -1);

    public static final int[] NO_SELECTION = _NONE.toArray();

    private final DateTimeFormatter _titlePrinter;
    private final java.util.Calendar _cal;
    private final LocalResourceManager _resourceManger;
    private final int _weekdayCount;

    private YearMonth _minimum;
    private YearMonth _maximum;
    private YearMonth _month;
    private YearMonthDay _date;

    private Color _alternateBackgroundColor;
    private Color _selectionColor;

    private Block _title;
    private Block[] _blocks;

    private boolean _requiresRefresh;

    public Calendar(Composite parent, int style) {
        this(parent, style, Locale.getDefault());
    }

    public Calendar(Composite parent, int style, Locale locale) {
        super(parent, style);
        final DateFormatSymbols symbols = DateFormatSymbols.getInstance(locale);
        _cal = java.util.Calendar.getInstance(locale);
        _titlePrinter = new DateTimeFormatterBuilder()//
                .appendMonthOfYearText()//
                .appendLiteral(' ')//
                .appendYear(4, 4)//
                .toFormatter();

        _resourceManger = new LocalResourceManager(JFaceResources.getResources(getDisplay()));

        _date = new YearMonthDay(_cal);
        _month = _date.toYearMonth();
        _minimum = _month.addYears(-100);
        _maximum = _month.addYears(100);

        final String[] weekdays = createCalendarWeekdays(_cal, symbols);
        _weekdayCount = weekdays.length;

        final Display display = getDisplay();
        _alternateBackgroundColor = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
        _selectionColor = display.getSystemColor(SWT.COLOR_LIST_SELECTION);

        final GridLayout layout = new GridLayout(_weekdayCount, false);
        layout.horizontalSpacing = 0;
        layout.marginHeight = 1;
        layout.marginWidth = 1;
        layout.verticalSpacing = 0;

        super.setLayout(layout);
        super.setBackground(display.getSystemColor(SWT.COLOR_WHITE));

        final Font titleFont = _resourceManger.createFont(FontDescriptor.createFrom("Arial", 12, SWT.BOLD));

        _title = createBlock();
        _title.setLayoutData(createLayoutData().span(_weekdayCount, 1).create());
        _title.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
        _title.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
        _title.setFont(titleFont);

        final Font calendarFont = _resourceManger.createFont(FontDescriptor.createFrom("Arial", 10, SWT.NONE));
        _blocks = new Block[_weekdayCount * ROW_COUNT];
        for (int i = 0; i < _blocks.length; i++) {
            if (i == _weekdayCount)
                createSeparator();

            final Block block = _blocks[i] = createBlock();
            block.setLayoutData(createLayoutData().create());
            block.setFont(calendarFont);

            if (i < _weekdayCount) {
                block.setText(weekdays[i]);
                continue;
            }

        }

        _requiresRefresh = true;

        new ListenerImpl();

        final PassthoughEventListener pListener = new PassthoughEventListener(this);
        pListener.addSource(_title);
        for (int i = 0; i < _blocks.length; i++)
            pListener.addSource(_blocks[i]);
    }

    @Override
    public void setBackground(Color color) {
        super.setBackground(color);
        for (int i = 0; i < _weekdayCount; i++) {
            _blocks[i].setBackground(color);
        }

        _requiresRefresh = true;
        redraw();
    }

    public void setAlternateBackground(Color color) {
        checkWidget();
        _alternateBackgroundColor = color;

        _requiresRefresh = true;
        redraw();
    }

    public void setSelectionColor(Color color) {
        checkWidget();
        _selectionColor = color;

        _requiresRefresh = true;
        redraw();
    }

    @Override
    public void setForeground(Color color) {
        super.setForeground(color);
        for (int i = 0; i < _blocks.length; i++) {
            _blocks[i].setForeground(color);
        }
    }

    @Override
    public void setFont(Font font) {
        super.setFont(font);
        for (int i = 0; i < _blocks.length; i++) {
            _blocks[i].setFont(font);
        }
        layout(true, true);
    }

    public void setTitleBackground(Color color) {
        checkWidget();
        _title.setBackground(color);
    }

    public void setTitleForeground(Color color) {
        checkWidget();
        _title.setForeground(color);
    }

    public void setTitleFont(Font font) {
        checkWidget();
        _title.setFont(font);
        layout(true, true);
    }

    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        _title.setEnabled(enabled);
        for (int i = 0; i < _blocks.length; i++) {
            _blocks[i].setEnabled(enabled);
        }
    }

    public int[] getMinimum() {
        checkWidget();
        return _minimum.toArray();
    }

    public void setMinimum(int year, int month) {
        checkWidget();
        final YearMonth candidate = new YearMonth(year, month);
        checkYearMonth(candidate);
        checkRange(_cal, candidate, _maximum);
        checkRange(_cal, candidate, _month);
        checkRange(_cal, candidate, _date.toYearMonth());
        _minimum = candidate;
    }

    public int[] getMaximum() {
        checkWidget();
        return _maximum.toArray();
    }

    public void setMaximum(int year, int month) {
        checkWidget();
        final YearMonth candidate = new YearMonth(year, month);
        checkYearMonth(candidate);
        checkRange(_cal, _minimum, candidate);
        checkRange(_cal, _month, candidate);
        checkRange(_cal, _date.toYearMonth(), candidate);
        _maximum = candidate;
    }

    public int[] getSelection() {
        checkWidget();
        return _date.toArray();
    }

    public void setSelection(int year, int month, int day) {
        checkWidget();
        final YearMonthDay candidate = new YearMonthDay(year, month, day);
        if (candidate.equals(_date))
            return;

        checkYearMonthDay(candidate);
        checkRange(_cal, _minimum, candidate.toYearMonth());
        checkRange(_cal, candidate.toYearMonth(), _maximum);

        internalSetSelection(candidate);
    }

    public void addDays(int days) {
        checkWidget();
        if (_NONE.equals(_date))
            return;

        _date.apply(_cal);
        _cal.add(DAY_OF_MONTH, days);
        final YearMonthDay candidate = new YearMonthDay(_cal);
        checkYearMonthDay(candidate);
        checkRange(_cal, _minimum, candidate.toYearMonth());
        checkRange(_cal, candidate.toYearMonth(), _maximum);

        internalSetSelection(candidate);
    }

    public void scrollTo(int year, int month) {
        checkWidget();

        final YearMonth candidate = new YearMonth(year, month);
        if (_month.equals(candidate))
            return;

        checkYearMonth(candidate);
        checkRange(_cal, _minimum, candidate);
        checkRange(_cal, candidate, _maximum);

        internalScrollTo(candidate);
    }

    @Override
    public Point computeSize(int wHint, int hHint, boolean changed) {
        final GC gc = new GC(this);
        gc.setFont(getFont());
        int width = (gc.stringExtent("33").x + 4) * _weekdayCount + 2;
        int height = (gc.getFontMetrics().getHeight() + 4) * ROW_COUNT + 2;

        gc.setFont(_title.getFont());
        height += gc.getFontMetrics().getHeight();
        width = Math.max(gc.getFontMetrics().getAverageCharWidth() * 14 + 10, width);

        gc.dispose();

        if (wHint != SWT.DEFAULT)
            width = wHint;

        if (hHint != SWT.DEFAULT)
            height = hHint;

        return new Point(width, height);
    }

    private void internalSetSelection(YearMonthDay selection) {
        if (selection.equals(_date))
            return;

        _date = selection;
        notifyListeners(SWT.Selection, new Event());

        _requiresRefresh = true;
        redraw();
    }

    private void internalScrollTo(YearMonth value) {
        if (_month.equals(value) || _NONE.toYearMonth().equals(value))
            return;

        _month = value;

        _requiresRefresh = true;
        redraw();
    }

    private void computeCalendarStart() {
        _month.apply(_cal);
        _cal.add(DAY_OF_MONTH, _cal.getFirstDayOfWeek() - _cal.get(DAY_OF_WEEK));
    }

    private void internalRefresh() {
        _month.apply(_cal);
        _title.setText(_titlePrinter.print(toLocalDate(_cal)));

        computeCalendarStart();

        for (int i = _weekdayCount; i < _blocks.length; i++) {
            final YearMonthDay ymd = new YearMonthDay(_cal);

            final Block block = _blocks[i];
            block.setText(Integer.toString(_cal.get(DAY_OF_MONTH)));
            // block.setEnabled(_month.equals(ymd.toYearMonth()));
            block.setBackground(computeBackground(ymd));

            _cal.add(DAY_OF_MONTH, 1);
        }

        layout(true, true);
    }

    private Color computeBackground(YearMonthDay ymd) {
        if (ymd.equals(_date))
            return _selectionColor;

        if (!ymd.toYearMonth().equals(_month))
            return _alternateBackgroundColor;

        return getBackground();
    }

    private YearMonthDay computeNeighbor(int x, int y) {
        if (_NONE.equals(_date))
            return _NONE;

        _date.apply(_cal);
        _cal.add(DAY_OF_MONTH, x);
        _cal.add(DAY_OF_MONTH, y * _weekdayCount);
        return new YearMonthDay(_cal);
    }

    private YearMonthDay computePage(YearMonthDay ymd, int x, int y) {
        if (_NONE.equals(ymd))
            return _NONE;

        ymd.apply(_cal);
        _cal.add(MONTH, y);
        _cal.add(YEAR, x);
        return new YearMonthDay(_cal);
    }

    private YearMonth computePage(YearMonth ym, int x, int y) {
        if (_NONE.toYearMonth().equals(ym))
            return _NONE.toYearMonth();

        ym.apply(_cal);
        _cal.add(MONTH, y);
        _cal.add(YEAR, x);
        return new YearMonth(_cal);
    }

    private YearMonthDay computeHome() {
        if (_NONE.equals(_date))
            return _NONE;

        _date.apply(_cal);
        final int sow = _cal.getFirstDayOfWeek();
        final int dow = _cal.get(DAY_OF_WEEK);
        _cal.add(DAY_OF_MONTH, sow - dow);
        return new YearMonthDay(_cal);
    }

    private YearMonthDay computeEnd() {
        if (_NONE.equals(_date))
            return _NONE;

        _date.apply(_cal);
        final int sow = _cal.getFirstDayOfWeek();
        final int dow = _cal.get(DAY_OF_WEEK);
        _cal.add(DAY_OF_MONTH, sow - dow + _weekdayCount - 1);
        return new YearMonthDay(_cal);
    }

    private void navigateTo(YearMonthDay candidate) {
        if (candidate == null)
            return;

        if (!_NONE.equals(candidate)) {
            if (candidate.compareTo(_minimum) < 0) {
                _minimum.apply(_cal);
                _cal.set(DAY_OF_MONTH, _cal.getActualMinimum(DAY_OF_MONTH));
                candidate = new YearMonthDay(_cal);
            } else if (candidate.compareTo(_maximum) > 0) {
                _maximum.apply(_cal);
                _cal.set(DAY_OF_MONTH, _cal.getActualMaximum(DAY_OF_MONTH));
                candidate = new YearMonthDay(_cal);
            }
        }

        internalSetSelection(candidate);
        internalScrollTo(candidate.toYearMonth());
    }

    private void keyPressed(Event event) {
        YearMonth scroll = null;
        YearMonthDay candidate = null;
        switch (event.keyCode) {
        case SWT.ARROW_DOWN:
            if (event.stateMask == SWT.MOD1) {
                if (_NONE.equals(_date)) {
                    scroll = computePage(_month, 0, 1);
                } else {
                    candidate = computePage(_date, 0, 1);
                }
            } else if (event.stateMask == SWT.NONE) {
                candidate = computeNeighbor(0, 1);
            }
            break;

        case SWT.ARROW_UP:
            if (event.stateMask == SWT.MOD1) {
                if (_NONE.equals(_date)) {
                    scroll = computePage(_month, 0, -1);
                } else {
                    candidate = computePage(_date, 0, -1);
                }
            } else if (event.stateMask == SWT.NONE) {
                candidate = computeNeighbor(0, -1);
            }
            break;

        case SWT.ARROW_RIGHT:
            if (event.stateMask == SWT.CTRL) {
                if (_NONE.equals(_date)) {
                    scroll = computePage(_month, 1, 0);
                } else {
                    candidate = computePage(_date, 1, 0);
                }
            } else if (event.stateMask == SWT.MOD1) {
                candidate = computeEnd();
            } else if (event.stateMask == SWT.NONE) {
                candidate = computeNeighbor(1, 0);
            }
            break;

        case SWT.ARROW_LEFT:
            if (event.stateMask == SWT.CTRL) {
                if (_NONE.equals(_date)) {
                    scroll = computePage(_month, -1, 0);
                } else {
                    candidate = computePage(_date, -1, 0);
                }
            } else if (event.stateMask == SWT.MOD1) {
                candidate = computeHome();
            } else if (event.stateMask == SWT.NONE) {
                candidate = computeNeighbor(-1, 0);
            }
            break;

        case SWT.HOME:
            candidate = computeHome();
            break;

        case SWT.END:
            candidate = computeEnd();
            break;

        case SWT.PAGE_UP:
            if (_NONE.equals(_date)) {
                scroll = computePage(_month, 0, -1);
            } else {
                candidate = computePage(_date, 0, -1);
            }
            break;

        case SWT.PAGE_DOWN:
            if (_NONE.equals(_date)) {
                scroll = computePage(_month, 0, 1);
            } else {
                candidate = computePage(_date, 0, 1);
            }
            break;
        }

        if (candidate != null)
            navigateTo(candidate);

        if (scroll != null)
            internalScrollTo(scroll);
    }

    private void mouseDown(Event event) {
        for (int i = _weekdayCount; i < _blocks.length; i++) {
            if (event.widget == _blocks[i]) {
                computeCalendarStart();
                _cal.add(DAY_OF_MONTH, i - _weekdayCount);
                final YearMonthDay candidate = new YearMonthDay(_cal);
                navigateTo(candidate);
            }
        }
    }

    private static String[] createCalendarWeekdays(java.util.Calendar calendar, DateFormatSymbols symbols) {
        final String[] weekdays = symbols.getShortWeekdays();
        final int min = calendar.getMinimum(DAY_OF_WEEK);
        final int days = calendar.getMaximum(DAY_OF_WEEK) - min + 1;
        final int firstWeekday = calendar.getFirstDayOfWeek();
        final String[] result = new String[days];
        for (int i = 0; i < result.length; i++) {
            final String weekday = weekdays[(firstWeekday + i - min) % days + min].substring(0, 1);
            result[i] = weekday;
        }

        return result;
    }

    private Block createBlock() {
        final Block block = new Block(this);
        block.setForeground(getForeground());
        block.setBackground(getBackground());
        block.setFont(getFont());
        block.setAlignment(SWT.CENTER);
        return block;
    }

    private void createSeparator() {
        final Label separator = new Label(this, SWT.SEPARATOR | SWT.HORIZONTAL | SWT.CENTER);
        separator.setLayoutData(createLayoutData()//
                .span(_weekdayCount, 1)//
                .align(SWT.FILL, SWT.CENTER)//
                .grab(true, false)//
                .create());
    }

    private GridDataFactory createLayoutData() {
        return GridDataFactory//
                .fillDefaults()//
                .grab(true, true);
    }

    private void checkYearMonth(YearMonth candidate) {
        if (_NONE.toYearMonth().equals(candidate))
            return;

        _cal.clear();
        final int year = candidate.year();
        if (_cal.getActualMinimum(YEAR) > year || _cal.getActualMaximum(YEAR) < year)
            error(ERROR_INVALID_ARGUMENT);

        _cal.set(YEAR, year);

        final int zeroBasedMonth = candidate.month() - 1;
        if (_cal.getActualMinimum(MONTH) > zeroBasedMonth || _cal.getActualMaximum(MONTH) < zeroBasedMonth)
            error(ERROR_INVALID_ARGUMENT);
    }

    private void checkYearMonthDay(YearMonthDay value) {
        if (_NONE.equals(value))
            return;

        checkYearMonth(value.toYearMonth());

        _cal.set(MONTH, value.month() - 1);

        final int day = value.day();
        if (_cal.getActualMinimum(DAY_OF_MONTH) > day || _cal.getActualMaximum(DAY_OF_MONTH) < day)
            error(ERROR_INVALID_ARGUMENT);
    }

    private static void checkRange(java.util.Calendar calendar, YearMonth minimum, YearMonth maximum) {
        if (_NONE.toYearMonth().equals(minimum) || _NONE.toYearMonth().equals(maximum))
            return;

        final java.util.Calendar min = (java.util.Calendar) calendar.clone();
        minimum.apply(min);

        final java.util.Calendar max = (java.util.Calendar) calendar.clone();
        maximum.apply(max);

        if (min.after(max))
            error(ERROR_INVALID_RANGE);
    }

    private class ListenerImpl implements Listener {

        ListenerImpl() {
            addListener(SWT.KeyDown, this);
            addListener(SWT.Dispose, this);
            addListener(SWT.Paint, this);

            for (int i = 0; i < _blocks.length; i++) {
                if (i >= _weekdayCount)
                    _blocks[i].addListener(SWT.MouseDown, this);

                _blocks[i].addListener(SWT.MouseVerticalWheel, this);
                _blocks[i].addListener(SWT.MouseHorizontalWheel, this);
            }
        }

        private void dispose() {
            removeListener(SWT.Dispose, this);
            removeListener(SWT.MouseDown, this);
            removeListener(SWT.Paint, this);

            for (int i = 0; i < _blocks.length; i++) {
                if (i >= _weekdayCount)
                    _blocks[i].removeListener(SWT.MouseDown, this);

                _blocks[i].removeListener(SWT.MouseVerticalWheel, this);
                _blocks[i].removeListener(SWT.MouseHorizontalWheel, this);
            }

            _resourceManger.dispose();
        }

        @Override
        public void handleEvent(Event event) {
            if (event.type == SWT.Paint) {
                if (_requiresRefresh) {
                    internalRefresh();
                    _requiresRefresh = false;
                }
                return;
            }
            if (event.type == SWT.KeyDown) {
                keyPressed(event);
                return;
            }

            if (event.type == SWT.MouseDown) {
                mouseDown(event);
                return;
            }

            if (event.type == SWT.MouseHorizontalWheel) {
                if (_NONE.equals(_date)) {
                    final YearMonth candidate = computePage(_month, event.count * -1, 0);
                    internalScrollTo(candidate);
                } else {
                    final YearMonthDay candidate = computePage(_date, event.count * -1, 0);
                    navigateTo(candidate);
                }
                return;
            }

            if (event.type == SWT.MouseVerticalWheel) {
                if (_NONE.equals(_date)) {
                    final YearMonth candidate = computePage(_month, 0, event.count * -1);
                    internalScrollTo(candidate);
                } else {
                    final YearMonthDay candidate = computePage(_date, 0, event.count * -1);
                    navigateTo(candidate);
                }
                return;
            }

            if (event.type == SWT.Dispose) {
                dispose();
                return;
            }

        }

    }

    private static class YearMonth implements Comparable<YearMonth> {
        private final int _year;
        private final int _month;

        public YearMonth(int year, int month) {
            _year = year;
            _month = month;
        }

        public YearMonth(java.util.Calendar calendar) {
            _year = calendar.get(YEAR);
            _month = calendar.get(MONTH) + 1;
        }

        public void apply(java.util.Calendar calendar) {
            calendar.clear();
            calendar.set(YEAR, _year);
            calendar.set(MONTH, _month - 1);
            calendar.set(DAY_OF_MONTH, 1);
        }

        public int year() {
            return _year;
        }

        public int month() {
            return _month;
        }

        public YearMonth addYears(int years) {
            return new YearMonth(_year + years, _month);
        }

        @Override
        public int compareTo(YearMonth o) {
            final int i = _year - o._year;
            if (i != 0)
                return i;

            return _month - o._month;
        }

        public int[] toArray() {
            return new int[] { _year, _month };
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + _month;
            result = prime * result + _year;
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            YearMonth other = (YearMonth) obj;
            if (_month != other._month)
                return false;
            if (_year != other._year)
                return false;
            return true;
        }

        @Override
        public String toString() {
            return String.format("year: %1$4d month: %2$2d", _year, _month);
        }
    }

    private static final class YearMonthDay implements Comparable<YearMonth> {

        private final YearMonth _yearMonth;
        private final int _day;

        public YearMonthDay(int year, int month, int day) {
            _yearMonth = new YearMonth(year, month);
            _day = day;
        }

        public YearMonthDay(java.util.Calendar calendar) {
            _yearMonth = new YearMonth(calendar);
            _day = calendar.get(DAY_OF_MONTH);
        }

        public void apply(java.util.Calendar calendar) {
            _yearMonth.apply(calendar);
            calendar.set(DAY_OF_MONTH, _day);
        }

        public int month() {
            return _yearMonth.month();
        }

        public int day() {
            return _day;
        }

        @Override
        public int compareTo(YearMonth o) {
            return _yearMonth.compareTo(o);
        }

        public YearMonth toYearMonth() {
            return _yearMonth;
        }

        public int[] toArray() {
            final int[] base = _yearMonth.toArray();
            final int[] arr = Arrays.copyOf(base, base.length + 1);
            arr[base.length] = _day;
            return arr;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + _day;
            result = prime * result + ((_yearMonth == null) ? 0 : _yearMonth.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            YearMonthDay other = (YearMonthDay) obj;
            if (_day != other._day)
                return false;
            if (_yearMonth == null) {
                if (other._yearMonth != null)
                    return false;
            } else if (!_yearMonth.equals(other._yearMonth))
                return false;
            return true;
        }

        @Override
        public String toString() {
            return String.format("%1$s day: %2$2d", super.toString(), _day);
        }
    }
}