Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CodenameOne/src/com/codename1/ui/Calendar.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ public class Calendar extends Container implements ActionSource {
private boolean changesSelectedDateEnabled = true;
private TimeZone tmz;
private long SELECTED_DAY = -1;
/// The exact millis the caller passed to {@link #setDate(Date)} /
/// {@link #setSelectedDate(Date)}, before the day-cell-comparison
/// normalisation in MonthView.setSelectedDay. Used by {@link #getDate()}
/// so that the time-of-day round-trips. -1 means "no user-supplied date,
/// fall back to the normalised SELECTED_DAY". See #1515.
private long originalSelectedDay = -1;
/// The exact millis the caller passed to {@link #setCurrentDate(Date)}.
/// See {@link #originalSelectedDay}.
private long originalCurrentDay = -1;
private boolean multipleSelectionEnabled = false;
private String selectedDaysUIID = "CalendarMultipleDay";

Expand Down Expand Up @@ -296,6 +305,10 @@ void componentChanged() {
///
/// the date object matching the current selection
public Date getDate() {
// Preserve the caller's original time-of-day when possible. See #1515.
if (originalSelectedDay >= 0) {
return new Date(originalSelectedDay);
}
return new Date(mv.getSelectedDay());
}

Expand All @@ -305,6 +318,8 @@ public Date getDate() {
///
/// - `d`: new date
public void setDate(Date d) {
originalSelectedDay = d.getTime();
originalCurrentDay = d.getTime();
mv.setSelectedDay(d.getTime());
mv.setCurrentDay(SELECTED_DAY, true);
componentChanged();
Expand Down Expand Up @@ -342,6 +357,7 @@ public void setYearRange(int minYear, int maxYear) {
///
/// - `d`: the selected day
public void setSelectedDate(Date d) {
originalSelectedDay = d.getTime();
mv.setSelectedDay(d.getTime());
mv.setCurrentDay(SELECTED_DAY, true);
componentChanged();
Expand All @@ -353,6 +369,10 @@ public void setSelectedDate(Date d) {
///
/// the currently viewed date
public Date getCurrentDate() {
// Preserve the caller's original time-of-day when possible. See #1515.
if (originalCurrentDay >= 0) {
return new Date(originalCurrentDay);
}
return new Date(mv.getCurrentDay());
}

Expand All @@ -363,6 +383,7 @@ public Date getCurrentDate() {
///
/// - `d`: the date to set the calendar view on.
public void setCurrentDate(Date d) {
originalCurrentDay = d.getTime();
mv.setCurrentDay(d.getTime(), true);
componentChanged();
}
Expand Down Expand Up @@ -1341,6 +1362,13 @@ public void actionPerformed(ActionEvent evt) {
setDayUIID(components[iter], "CalendarSelectedDay");

SELECTED_DAY = dates[iter];
// A user tap supplies only the day, so the
// hour/minute information from a prior
// setDate(Date) call is no longer authoritative.
// Clear the cached original so getDate() falls
// back to the day-cell normalised value. See #1515.
Calendar.this.originalSelectedDay = -1;
Calendar.this.originalCurrentDay = -1;
selected = components[iter];
}
fireActionEvent();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.codename1.ui;

import com.codename1.junit.FormTest;
import com.codename1.junit.UITestBase;

import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

import static org.junit.jupiter.api.Assertions.*;

/**
* Regression tests for
* https://github.com/codenameone/CodenameOne/issues/1515
* -- the Calendar UI component must preserve the hour/minute/second/millis
* of the Date passed into setDate, setCurrentDate and setSelectedDate. The
* 2015 reporter found that MonthView.setSelectedDay and setCurrentDay were
* forcibly normalising the time-of-day, so round-tripping through getDate
* / getCurrentDate silently lost the time component.
*/
class CalendarDatePreservationTest extends UITestBase {

private static Date local(int year, int month, int dayOfMonth, int hour, int minute, int second, int millis) {
GregorianCalendar cal = new GregorianCalendar();
cal.clear();
cal.set(year, month, dayOfMonth, hour, minute, second);
cal.set(java.util.Calendar.MILLISECOND, millis);
return cal.getTime();
}

private static void assertSameInstant(Date expected, Date actual) {
assertEquals(expected.getTime(), actual.getTime(),
"Date instant must round-trip exactly; got " + actual + ", expected " + expected);
}

@FormTest
void setDateRoundTripsHourMinuteSecondMillis() {
Calendar c = new Calendar();
Date in = local(2026, java.util.Calendar.MARCH, 15, 13, 45, 30, 500);
c.setDate(in);
assertSameInstant(in, c.getDate());
}

@FormTest
void setSelectedDateRoundTripsHourMinuteSecondMillis() {
Calendar c = new Calendar();
Date in = local(2026, java.util.Calendar.AUGUST, 1, 23, 59, 59, 999);
c.setSelectedDate(in);
assertSameInstant(in, c.getDate());
}

@FormTest
void setCurrentDateRoundTripsHourMinuteSecondMillis() {
Calendar c = new Calendar();
Date in = local(2026, java.util.Calendar.JANUARY, 1, 9, 30, 15, 250);
c.setCurrentDate(in);
assertSameInstant(in, c.getCurrentDate());
}

@FormTest
void setDateMidnightStillReturnsMidnight() {
// Pre-fix the time was forcibly set to 01:00, so midnight became 01:00.
Calendar c = new Calendar();
Date midnight = local(2026, java.util.Calendar.JULY, 4, 0, 0, 0, 0);
c.setDate(midnight);
assertSameInstant(midnight, c.getDate());
}

@FormTest
void dayOfMonthStillReportsCorrectlyAfterPreservingTimeOfDay() {
// The fix preserves the time-of-day on read but day-of-month math must
// still reflect the user-supplied day.
Calendar c = new Calendar();
Date in = local(2026, java.util.Calendar.MARCH, 15, 13, 45, 30, 500);
c.setDate(in);

GregorianCalendar verifier = new GregorianCalendar(TimeZone.getDefault());
verifier.setTime(c.getDate());
assertEquals(2026, verifier.get(java.util.Calendar.YEAR));
assertEquals(java.util.Calendar.MARCH, verifier.get(java.util.Calendar.MONTH));
assertEquals(15, verifier.get(java.util.Calendar.DAY_OF_MONTH));
}
}
Loading