I have a few questions about the java Date class, it's really confusing me.
I have two problems:
I have a date object, and I want to see if the date stored in it is later than the begin of the current day or not? So if it's later than today 00:00am or not?
The second problem is, I want to group several date objects by day. But again, I don't understand how I should check if it's within the 24 hours of a specific date.
I think they're kind of related, once I have the 'day' for a date object it can't be to hard?
Any help is really appreciated, the Date class is really confusing me..
The java Date / Calendar classes are really poorly implemented.
Sounds like you should use joda time - specificly LocalDate.
http://joda-time.sourceforge.net/key_partial.html
http://joda-time.sourceforge.net/api-release/org/joda/time/LocalDate.html
This should solve both #1 and #2.
FYI - JodaTime will be replacing the JDK dates eventually (jsr 310)
Rough grouping solution:
public Map<LocalDate, List<Foo>> groupByDate(List<Foo> foos) {
Map<LocalDate, List<Foo>> groupedFoo = new HashMap()<..>
for(Foo foo : foos) {
Date javaDate = foo.getDate();
LocalDate jodaDate = new LocalDate(javaDate);
List<Foo> dateGroupedFoos = groupedFoo.get(jodaDate);
if (null == dateGroupedFoos) {
dateGroupedFoos = new ArrayList()<..>;
groupedFoo.put(jodaDate, dateGroupedFoos);
}
dateGroupedFoos.add(foo);
}
}
I have a date object, and I want to see if the date stored in it is later than the begin of the current day or not? So if it's later than today 00:00am or not?
Create a date for today
Reset hours minutes seconds etc
Use the before method to compare.
The second problem is, I want to group several date objects by day. But again, I don't understand how I should check if it's within the 24 hours of a specific date.
Just group by year, month and date field, or reset the hours, minutes and seconds fields.
Days do not always start at 00:00
So if it's later than today 00:00am or not
Be aware that in some time zones on some dates, the day does not begin at 00:00:00. Anomalies such as Daylight Saving Time (DST) mean the day may start at another time, such as 01:00:00.
Time zone
Determining the start of the day requires a date. Determining a date requires a time zone.
A time zone is crucial in determining a date. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.
Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter pseudo-zones such as EST or IST as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate
The LocalDate class represents a date-only value without time-of-day and without time zone.
LocalDate today = LocalDate.now( z ) ;
ZonedDateTime
Let java.time determine the first moment of that date. Specify a ZoneId to get a ZonedDateTime object.
ZonedDateTime todayStart = today.atStartOfDay( z ) ; // Determine the first moment of the day in a particular time zone.
Comparison
I want to see if the date stored in it is later than the begin of the current day or not?
Use the comparison methods, isBefore, isAfter, and isEqual.
boolean isFuture = ! ( someZdt.isBefore( todayStart ) ) ; // A shorter way to say "is equal to or later" --> "is NOT before".
Map & Set
I want to group several date objects by day … I don't understand how I should check if it's within the 24 hours of a specific date.
First of all, remember that days are not always 24 hours long. Because of anomalies such as DST, they may be 23 or 25 hours long, or some other length.
So if you want to track them by date, use LocalDate rather than hours. You can interrogate each ZonedDateTime for its LocalDate.
Use a Map, with the date-only portion (LocalDate) as a key, and with the ZonedDateTime objects for that date kept in a Collection such as Set for the map entry’s value.
Map < LocalDate, Set < ZonedDateTime > > map = new HashMap <>( );
Here's a full snippet of example code. You can run this code live at IdeOne.com.
Map < LocalDate, Set < ZonedDateTime > > map = new HashMap <>( );
Set < ZonedDateTime > set = null;
ZoneId z = ZoneId.of( "Africa/Tunis" );
LocalDate today = LocalDate.now( z );
ZonedDateTime todayStart = today.atStartOfDay( z ); // Determine the first moment of the day in a particular time zone.
map.putIfAbsent( todayStart.toLocalDate( ) , new HashSet <>( ) );
set = map.get( todayStart.toLocalDate( ) ) ;
set.add( todayStart );
ZonedDateTime zdt1 = todayStart.plusHours( 6 );
map.putIfAbsent( zdt1.toLocalDate( ) , new HashSet <>( ) );
set = map.get( zdt1.toLocalDate( ) ) ;
set.add( zdt1 );
ZonedDateTime zdt2 = todayStart.plusDays( 2 );
map.putIfAbsent( zdt2.toLocalDate( ) , new HashSet <>( ) );
set = map.get( zdt2.toLocalDate( ) ) ;
set.add( zdt2 );
ZonedDateTime zdt3 = todayStart.plusMonths( 4 );
map.putIfAbsent( zdt3.toLocalDate( ) , new HashSet <>( ) );
set = map.get( zdt3.toLocalDate( ) ) ;
set.add( zdt3 );
System.out.println( "map.toString(): " + map );
We expect to see 3 entries in the map, with one of those values being a set of two items while the other two values are a set of one item each. Read this output carefully, as a HashSet does not return items by the order in which they were put.
map.toString(): {2018-01-25=[2018-01-25T00:00+01:00[Africa/Tunis]], 2018-01-23=[2018-01-23T00:00+01:00[Africa/Tunis], 2018-01-23T06:00+01:00[Africa/Tunis]], 2018-05-23=[2018-05-23T00:00+01:00[Africa/Tunis]]}
Let's break that apart for clarity. We see that January 23 has a pair of values in its set, while January 25 and May 23 both have a single.
2018-01-25=[2018-01-25T00:00+01:00[Africa/Tunis]]
2018-01-23=[2018-01-23T00:00+01:00[Africa/Tunis], 2018-01-23T06:00+01:00[Africa/Tunis]],
2018-05-23=[2018-05-23T00:00+01:00[Africa/Tunis]]
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android, the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
Related
I am using java.util.Calendar to find the start of a given week using its set() methods.
This works perfectly on Android Nougat+, but not on any Android version below Marshmallow.
I have tested on both physical devices and emulators.
I have used the debugger to verify that the problem lies with the Calendar code, not some issue in displaying it.
I have used Kotlin and Java to create different minimal examples, and the issue persists in both.
Here is the Kotlin minimal example, where a TextView displays the date and a Button is used to increase that date by a week:
class MainActivity : AppCompatActivity() {
var week = 10
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Set TextView to show the date of the 10th week in 2018.
setCalendarText(week)
// Increase the week on every button click, and show the new date.
button.setOnClickListener { setCalendarText(++week) }
}
/**
* Set the text of a TextView, defined in XML, to the date of
* a given week in 2018.
*/
fun setCalendarText(week: Int) {
val cal = Calendar.getInstance().apply {
firstDayOfWeek = Calendar.MONDAY
set(Calendar.YEAR, 2018)
set(Calendar.WEEK_OF_YEAR, week)
set(Calendar.DAY_OF_WEEK, Calendar.MONDAY)
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 1)
}
textView.text = SimpleDateFormat("dd MMMM yyyy", Locale.UK).format(cal.time)
}
}
When working as expected, the activity launches with the TextView set to display "05 March 2018". This value changes to the first day of every successive week when the button is clicked.
On Android Marshmallow and below:
The TextView's initial value is set to the start of the current week (03 September 2018).
The date does not change when the button is clicked.
The Calendar can correctly retrieve the last day of the current week if the day is set to Calendar.SUNDAY. It will not work for any other weeks.
Edit: I have attempted to create a Java MVCE, which allows you to perform a quick check whether the basic problem appears by running CalendarTester.test().
import android.util.Log;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
class CalendarTester {
/**
* Check that the Calendar returns the correct date for
* the start of the 10th week of 2018 instead of returning
* the start of the current week.
*/
public static void test() {
// en_US on my machine, but should probably be en_GB.
String locale = Locale.getDefault().toString();
Log.v("CalendarTester", "The locale is " + locale);
Long startOfTenthWeek = getStartOfGivenWeek(10);
String startOfTenthWeekFormatted = formatDate(startOfTenthWeek);
boolean isCorrect = "05 March 2018".equals(startOfTenthWeekFormatted);
Log.v("CalendarTester", String.format("The calculated date is %s, which is %s",
startOfTenthWeekFormatted, isCorrect ? "CORRECT" : "WRONG"));
}
public static Long getStartOfGivenWeek(int week) {
Calendar cal = Calendar.getInstance();
cal.setFirstDayOfWeek(Calendar.MONDAY);
cal.set(Calendar.YEAR, 2018);
cal.set(Calendar.WEEK_OF_YEAR, week);
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 1);
return cal.getTimeInMillis();
}
public static String formatDate(Long timeInMillis) {
return new SimpleDateFormat("dd MMMM yyyy", Locale.UK).format(timeInMillis);
}
}
tl;dr
Use the java.time classes back-ported to early Android.
Problem statement: From current date, move to previous or same Monday, then move to Monday of standard ISO 8601 week number 10 of that date’s week-based year, add one week, and generate text in standard ISO 8601 format for the resulting date.
org.threeten.bp.LocalDate.now( // Represent a date-only value, without time-of-day and without time zone.
ZoneId.of( "Europe/London" ) // Determining current date requires a time zone. For any given moment, the date and time vary around the globe by zone.
) // Returns a `LocalDate`. Per immutable objects pattern, any further actions generate another object rather than changing (“mutating”) this object.
.with(
TemporalAdjusters.previousOrSame( // Move to another date.
DayOfWeek.MONDAY // Specify desired day-of-week using `DayOfWeek` enum, with seven objects pre-defined for each day-of-week.
)
) // Renders another `LocalDate` object.
.with(
IsoFields.WEEK_OF_WEEK_BASED_YEAR ,
10
)
.plusWeeks( 1 )
.toString()
2018-03-12
Simplify the problem
When tracking down mysterious or buggy behavior, simply the programming to the barest minimum needed to reproduce the problem. In this case, strip away the supposedly irrelevant GUI code to focus on the date-time classes.
As in a scientific experiment, control for various variables. In this case, both time zone and Locale affect the behavior of Calendar. For one thing, the definition of a week within Calendar varies by Locale. So specify these aspects explicitly by hard-coding.
Set a specific date and time, as different times on different days in different zones can affect the behavior.
Calendar is a superclass with various implementations. If you are expecting GregorianCalendar, use that explicitly while debugging.
So, trying running something like the following across your tool scenarios to troubleshoot your problem.
TimeZone tz = TimeZone.getTimeZone( "America/Los_Angeles" );
Locale locale = Locale.US;
GregorianCalendar gc = new GregorianCalendar( tz , locale );
gc.set( 2018 , 9- 1 , 3 , 0 , 0 , 0 ); // Subtract 1 from month number to account for nonsensical month numbering used by this terrible class.
gc.set( Calendar.MILLISECOND , 0 ); // Clear fractional second.
System.out.println( "gc (original): " + gc.toString() );
System.out.println( gc.toZonedDateTime() + "\n" ); // Generate a more readable string, using modern java.time classes. Delete this line if running on Android <26.
int week = 10;
gc.set( Calendar.WEEK_OF_YEAR , week );
System.out.println( "gc (week=10): " + gc.toString() );
System.out.println( gc.toZonedDateTime() + "\n" );
int weekAfter = ( week + 1 );
gc.set( Calendar.WEEK_OF_YEAR , weekAfter );
System.out.println( "gc (weekAfter): " + gc.toString() );
System.out.println( gc.toZonedDateTime() + "\n" );
When run.
gc (original): java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2018,MONTH=8,WEEK_OF_YEAR=36,WEEK_OF_MONTH=2,DAY_OF_MONTH=3,DAY_OF_YEAR=251,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=2,AM_PM=1,HOUR=2,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]
2018-09-03T00:00-07:00[America/Los_Angeles]
gc (week=10): java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2018,MONTH=8,WEEK_OF_YEAR=10,WEEK_OF_MONTH=2,DAY_OF_MONTH=3,DAY_OF_YEAR=246,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]
2018-03-05T00:00-08:00[America/Los_Angeles]
gc (weekAfter): java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2018,MONTH=2,WEEK_OF_YEAR=11,WEEK_OF_MONTH=2,DAY_OF_MONTH=5,DAY_OF_YEAR=64,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-28800000,DST_OFFSET=0]
2018-03-12T00:00-07:00[America/Los_Angeles]
java.time
Really, your problem is moot because you should not be using the terrible old Calendar class at all. It is part of the troublesome old date-time classes that years ago were supplanted by the modern java.time classes. For early Android, see the last bullets at bottom below.
In Calendar/GregorianCalendar, the definition of a week varies by Locale, Not so in java.time by default, which uses the ISO 8601 standard definition of a week.
Week # 1 has the first Thursday of the calendar-year.
Monday is the first day of the week.
A week-based year has either 52 or 53 weeks.
The first/last few days of the calendar may appear in the previous/next week-based year.
LocalDate
The LocalDate class represents a date-only value without time-of-day and without time zone.
A time zone is crucial in determining a date. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.
If no time zone is specified, the JVM implicitly applies its current default time zone. That default may change at any moment during runtime(!), so your results may vary. Better to specify your desired/expected time zone explicitly as an argument.
Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( z ) ;
If you want to use the JVM’s current default time zone, ask for it and pass as an argument. If omitted, the JVM’s current default is applied implicitly. Better to be explicit, as the default may be changed at any moment during runtime by any code in any thread of any app within the JVM.
ZoneId z = ZoneId.systemDefault() ; // Get JVM’s current default time zone.
Or specify a date. You may set the month by a number, with sane numbering 1-12 for January-December.
LocalDate ld = LocalDate.of( 1986 , 2 , 23 ) ; // Years use sane direct numbering (1986 means year 1986). Months use sane numbering, 1-12 for January-December.
Or, better, use the Month enum objects pre-defined, one for each month of the year. Tip: Use these Month objects throughout your codebase rather than a mere integer number to make your code more self-documenting, ensure valid values, and provide type-safety.
LocalDate ld = LocalDate.of( 2018 , Month.SEPTEMBER , 3 ) ;
TemporalAdjuster
To move to a prior Monday, or stay on the date if already a Monday, use a TemporalAdjuster implementation provided in the TemporalAdjusters class. Specify desired day-of-week with DayOfWeek enum.
LocalDate monday = ld.with( TemporalAdjusters.previousOrSame( DayOfWeek.MONDAY ) ) ;
IsoFields
The java.time classes have limited support for weeks. Use the IsoFields class with its constants WEEK_OF_WEEK_BASED_YEAR & WEEK_BASED_YEAR.
LocalDate mondayOfWeekTen = monday.with( IsoFields.WEEK_OF_WEEK_BASED_YEAR , 10 ) ;
ISO 8601
The ISO 8601 standard defines many useful practical formats for representing date-time values as text. This includes weeks. Let's generate such text as output.
String weekLaterOutput =
weekLater
.get( IsoFields.WEEK_BASED_YEAR )
+ "-W"
+ String.format( "%02d" , weekLater.get( IsoFields.WEEK_OF_WEEK_BASED_YEAR ) )
+ "-"
+ weekLater.getDayOfWeek().getValue()
; // Generate standard ISO 8601 output. Ex: 2018-W11-1
Dump to console.
System.out.println("ld.toString(): " + ld);
System.out.println("monday.toString(): " +monday);
System.out.println("weekLater.toString(): " + weekLater);
System.out.println( "weekLaterOutput: " + weekLaterOutput ) ;
When run.
ld.toString(): 2018-09-03
monday.toString(): 2018-09-03
weekLater.toString(): 2018-03-12
weekLaterOutput: 2018-W11-1
Tip for Java (not Android): If doing much work with weeks, consider adding the ThreeTen-Extra library to access its YearWeek class.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
I've a Calendar object which is set to 2 years ago. I want to update this calendar object close to the current time by adding hours but it shouldn't past the current time.
For example, original date is June 24,2015- 11:20:52:200
Current time is Jul 14,2016- 14:08:30:100
I want to get the timestamp as Jul 14,2016-13:20:52:200
The update should be propagated backwards in days if required as well. In case the original time is June 24,2015 00:20:50:200, and the current time is June 27,2015 00:15:20:100, I need to get June 26,2016 23:20:200.
Is there a native method in Java 8 that gives this kind of functionality?
java.time
The java.util.Calendar class is now supplanted by the java.time framework is built into Java 8 and later. These classes supplant the old troublesome date-time classes such as java.util.Date, .Calendar, & java.text.SimpleDateFormat.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations.
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport and further adapted to Android in ThreeTenABP.
LocalTime
The LocalTime class represents a time-of-day alone, without a date and without a time zone.
We can extract a LocalTime from both your past date-time and your current date-time and compare. If the past LocalTime is same or earlier than current one, we can stick with current date-time and adjust to the past time-of-day. If the past LocalTime is after the current LocalTime, then we must use yesterday’s date.
ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime pastZdt = ZonedDateTime.of( 2015 , Month.June, 24 , 11 , 20 , 52 , 200_000_000 , zoneId );
ZonedDateTime nowZdt = ZonedDateTime.now( zoneId );
LocalTime pastTOD = pastZdt.toLocalTime();
LocalTime nowTOD = nowZdt.toLocalTime();
ZonedDateTime target = null;
if( pastTOD.isAfter( nowTOD ) ) { // Use yesterday’s date.
target = ZonedDateTime.of( nowZdt.toLocalDate().minusDays( 1 ) , pastTOD , zoneId );
} else { // Else, the past time-of-day is same or ealier than today’s time-of-day, so use today's date.
target = ZonedDateTime.of( nowZdt.toLocalDate() , pastTOD , zoneId );
}
Beware: That particular time-of-day may not be valid on this particular date because of anomalies such as Daylight Saving Time (DST). Be sure to read the class documentation to understand the resulting behavior that adjusts to fix such a predicament. That behavior may or may not meet your needs (no perfect remedy is possible).
Caveat: Above code was never run. Use at your own risk. Please edit to fix if you discover flaws.
how to compare three date/time values in java?
Currently i use Date objects
I mean:
Date1 - today, 10:00PM
Date2 - tomorrow, 5:00AM
Date3 - current time
Is date3 between date1 and date2, respect date AND time?
date1.compareTo(date3) * date2.compareTo(date3) > 0
and
date1.after(date3) && date2.before(date3)
are not working.
I use this code in Android App, and if i set my time to 11.30AM, it still returns true for above conditions. If i use Time-objects and 2nd method, it doesn't recognise my time span is between 2 days.
Any idea?
EDIT: To make it exact, here is my current code. app is something like an alarm clock.
// Current Date/Time
Date now = Calendar.getInstance().getTime();
// Time when user goes to bed (current day)
Date sleep = new Date(now.getYear(), now.getMonth(), now.getDate(), Shours, Sminutes);
// Time when user wakes up (next day)
// Get Next Day's Date and set Time
Calendar wk = Calendar.getInstance();
wk.setTime(sleep);
wk.set(Calendar.HOUR_OF_DAY, Whours);
wk.set(Calendar.MINUTE, Wminutes);
// tomorrow
wk.add(Calendar.DATE, 1);
// and convert to date
Date wake = wk.getTime();
// Compare
if(now.after(sleep) && now.before(wake)) {
Log.d("uSleep", "Debug: Night time");
}
else {
Log.d("uSleep", "Debug: Day Time");
}
Maybe it's still too hard to understand. Image you go to bed at 10PM and you get up at 5AM. Now how to find out if you're sleeping by comparing your "go to bed"-time and you "get up"-time to the current time. I need to use "tomorrow" for your "get up"-time, otherwise java seems to compare all times for the same day, which is impossible.
tl;dr
Interval.of(
start ,
stop
).contains(
ZonedDateTime.now( ZoneId.of( "America/Montreal" ) )
.toInstant()
)
Details
The question is confusing, but seems to be…
How do I tell if a particular moment occurs within a span of time?
java.time
I mean: Date1 - today, 10:00PM Date2 - tomorrow, 5:00AM Date3 - current time
A time zone is crucial in determining “today” and “tomorrow”. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.
Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime now = ZonedDateTime.now( z );
To get the same date with another time-of-day, extract LocalDate. The LocalDate class represents a date-only value without time-of-day and without time zone. Specify desired time-of-day with LocalTime. Combine with ZoneId to get a ZonedDateTime.
LocalDate today = now.toLocalDate() ;
LocalTime tenPm = LocalTime.of( 22 , 0 ) ; // 10 PM is 22:00.
ZonedDateTime tenPmToday = ZonedDateTime.of( today , tenPm , z ) ;
To get tomorrow, add one day to today's date.
LocalDate tomorrow = ld.plusDays( 1 ) ;
LocalTime fiveAm = LocalTime.of( 5 , 0 ) ;
ZonedDateTime fiveAmTomorrow = ZonedDateTime.of( tomorrow ,fiveAm , z ) ;
Compare
To compare, call the isBefore, isEqual, and isAfter methods.
Boolean contains = ( ! now.isBefore( tenPmToday ) ) && now.isBefore( fiveAmTomorrow ) ;
Of course, now will always be before tomorrow, so I'm not sure of your intentions here.
org.threeten.extra.Interval
You may find the Interval class useful for this work, from the ThreeTen-Extra project listed below. This class stores a pair of Instant objects, and has some handy comparison methods such as contains.
The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).
Interval interval = Interval.of( tenPmToday.toInstant() , fiveAmTomorrow.toInstant() ) ;
Boolean contains = interval.contains( now.toInstant() ) ;
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
Joda-Time
Update: the Joda-Time project is now in maintenance mode, with the team advising migration to the java.time classes.
The best way is to use the Joda-Time library, rather than the java.util.Date/.Calendar classes which are notoriously troublesome, confusing, and flawed.
Span Of Time
In Joda-Time you can represent a span of time in three ways: Interval, Period, and Duration. In this case, we need Interval, defined by a pair of specific points in the timeline, with the half-open [) approach where the beginning is inclusive and the ending exclusive.
The pair of specific points, as well as the current moment now, are all represented by the DateTime class. Unlike a java.util.Date, a DateTime knows its own assigned time zone. If unspecified, the JVM’s current default time zone will be applied. So generally better to specify.
Example Code
Some example code using Joda-Time 2.5.
DateTimeZone zone = DateTimeZone.forID( "America/Montreal" );
DateTime now = DateTime.now( zone );
DateTime bedtime = now.withTime( 22, 0, 0, 0 ); // Today’s bedtime. May be past, future, or this very moment now.
DateTime risetime = bedtime.plusHours( 7 ); // 05:00 next morning.
Interval sleepInterval = new Interval( bedtime, risetime );
boolean asleep = sleepInterval.contains( now ); // Half-Open "[)" comparison, beginning is inclusive, ending exclusive.
i have this code in my program:
if (expire.after(start) && expire.before(end))
//do somethig
But this code does not include the bound day.
I mean for example if start is 2014/01/15, end is 2014/01/20 and expire is 2014/01/20, expire is not considered. How to solve?
I tried in this way:
if (expire.after(start) && (expire.equals(end) || expire.before(end)))
But it doesn't work, i get the same result.
As #assylias stated you might try !expire.after(end) although this is probably not enough. If you instead use expire.equals(end) then you have to keep in mind that this is not just a temporal comparison! Here you also compare time zone informations, even the locale and so on. In general it is not good to use GregorianCalendar for only date comparisons because this type also knows milliseconds as time part. So you have probably either to manually set the whole time part of all three calendar instances to zero (midnight, leaving out time zone anomalies like in Brazil at certain days) or much better, you should instead extract the date informations and compare these details by using following tool:
public class DateComparator implements Comparator<GregorianCalendar> {
public int compare(GregorianCalendar gcal1, GregorianCalendar gcal2) {
int y1 = gcal1.get(Calendar.YEAR);
int y2 = gcal2.get(Calendar.YEAR);
if (y1 != y2) {
return y1 - y2;
}
int m1 = gcal1.get(Calendar.MONTH);
int m2 = gcal2.get(Calendar.MONTH);
if (m1 != m2) {
return m1 - m2;
}
return gcal1.get(Calendar.DATE) - gcal2.get(Calendar.DATE);
}
}
But make sure that you always have the same time zone when comparing the date parts.
Otherwise you can try JodaTime which offers the type LocalDate. And a similar date-only type is also contained in new Java 8.
When I have done time checks in the past I always used only after and before, even when I has a range check that was start <= current <= end.
You achieve this by adjusting the start and end dates such that they are outside the bounds of the desired range.
For example, if the start date is 2014/01/15, then use the start date value of 2014/01/14 23:59:59 as the actual start date that is used for the comparison. For the end date, instead of 2014/01/20, use 2014/01/21 00:00:00. With these values you can use .after(startDate) and .before(endDate).
Here is some code to calculate the start date:
private static Date calculateStartDate(
final int year,
final int month,
final int dayOfMonth)
{
Calendar calendar = Calendar.getInstance();
Date returnValue;
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, month);
calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
calendar.set(Calendar.HOUR, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
calendar.set(Calendar.MILLISECOND, 999);
returnValue = calendar.getTime();
return returnValue;
}
Edit added millisecond above
Or
if ((expire.compareTo(start) >= 0) && (expire.compareTo(end) <= 0))
since
http://docs.oracle.com/javase/7/docs/api/java/util/Calendar.html#after(java.lang.Object)
says .before is equivalent to compareTo(when) < 0 and .after is equivalent to compareTo(when) > 0
I finally solved adjusting the start and end dates such that they are outside the bounds of the desired range.
For example, if the start date is 2014/01/15, i set it to 2014/01/14 and if the end date is 2014/01/20, i set it to 2014/01/21. With these values i can use .after and .before methods with no problems.
Thank you to DwB for the suggestion and than you to all for your great support!
tl;dr
Use java.time.LocalDate for a date-only value.
( ! ( myLocalDate.isBefore( start ) ) // Is target date NOT before the start (meaning, is the target equal to or later than
&&
myLocalDate.isBefore( stop )
…or, better:
org.threeten.extra.LocalDateRange.of( // Half-Open range.
LocalDate.of( 2014 , Month.JANUARY , 15 ) , // Inclusive.
LocalDate.of( 2014 , Month.JANUARY , 20 ) // Exclusive.
).contains(
LocalDate.of( 2014 , Month.JANUARY , 11 )
)
false
GregorianCalendar is a troublesome obsolete class. Avoid it.
Details
I do not understand the logic of your business problem, but perhaps this example code will help.
I believe you'll find that working with time spans in the "half-open" approach works best. The start of the span is inclusive, and the ending is exclusive. For more discussion see another answer of mine with this chart…
java.time
Here is some code using the modern java.time classes built into Java 8 and later.
LocalDate
The LocalDate class represents a date-only value without time-of-day and without time zone.
A time zone is crucial in determining a date. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.
If no time zone is specified, the JVM implicitly applies its current default time zone. That default may change at any moment, so your results may vary. Better to specify your desired/expected time zone explicitly as an argument.
Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( z ) ;
If you want to use the JVM’s current default time zone, ask for it and pass as an argument. If omitted, the JVM’s current default is applied implicitly. Better to be explicit, as the default may be changed at any moment during runtime by any code in any thread of any app within the JVM.
ZoneId z = ZoneId.systemDefault() ; // Get JVM’s current default time zone.
Or specify a date. You may set the month by a number, with sane numbering 1-12 for January-December.
LocalDate ld = LocalDate.of( 1986 , 2 , 23 ) ; // Years use sane direct numbering (1986 means year 1986). Months use sane numbering, 1-12 for January-December.
Or, better, use the Month enum objects pre-defined, one for each month of the year. Tip: Use these Month objects throughout your codebase rather than a mere integer number to make your code more self-documenting, ensure valid values, and provide type-safety.
LocalDate ld = LocalDate.of( 1986 , Month.FEBRUARY , 23 ) ;
Comparing dates
The LocalDate class offers comparison methods: isBefore, isAfter, isEqual, equals, and compareTo.
Not exactly sure from your Question what your comparison rules are. But I guess you are missing the concept of using the ! operator for "NOT" condition.
Using the Half-Open approach we want to ask if the target date "is equal to or later than the beginning AND before than ending".
LocalDate start = LocalDate.of( 2014 , Month.JANUARY , 15 ) ;
LocalDate stop = LocalDate.of( 2014 , Month.JANUARY , 20 ) ;
boolean rangeContainsTarget = ( ld.isEqual( start ) || (ld.isAfter( start ) ) && ld.isBefore( stop ) ; // Half-Open test, the long-way. "is equal to or later than the beginning AND before than ending"
A shorter way to ask "is equal to or later than" is "is not before".
boolean rangeContainsTarget = ( ! ( ld.isBefore( start ) ) && ld.isBefore( stop ) ; // Half-Open test, the long-way. "is not before the beginning AND before than ending"
Range object
Add the ThreeTen-Extra library to your project to access the LocalDateRange class. It defines a range of time as a pair of LocalDate objects. The class offers handy methods such as abuts, contains, intersection, and more. It uses the Half-Open approach.
LocalDateRange range = LocalDateRange.of( start , stop ) ;
boolean rangeContainsTarget = range.contains( target ) ;
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android, the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
Joda-Time
UPDATE: The Joda-Time project is now in maintenance mode, with the team advising migration to the java.time classes. This section left intact for history.
Look at this code using the Joda-Time 2.3 library.
// Specify a time zone rather than rely on default.
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Oslo" );
DateTime event = new DateTime( 2014, 1, 20, 23, 59, 59, timeZone );
DateTime begin = new DateTime( 2014, 01, 15, 0, 0, 0, timeZone ); // Inclusive
// After the stroke of midnight of the 20th, on the first moment of the new day (the 21st), event is late.
DateTime end = new DateTime( 2014, 01, 21, 0, 0, 0, timeZone ); // Exclusive. All split-second moments before this are acceptable, but this moment and onwards is not acceptable.
Interval interval = new Interval( begin, end );
boolean isEventOnSchedule = interval.contains( event );
Dump to console…
System.out.println( "event: " + event );
System.out.println( "begin: " + begin );
System.out.println( "end: " + end );
System.out.println( "interval: " + interval );
System.out.println( "isEventOnSchedule: " + isEventOnSchedule );
When run…
event: 2014-01-20T23:59:59.000+01:00
begin: 2014-01-15T00:00:00.000+01:00
end: 2014-01-21T00:00:00.000+01:00
interval: 2014-01-15T00:00:00.000+01:00/2014-01-21T00:00:00.000+01:00
isEventOnSchedule: true
I need to save a few dates in SharedPreferences in android and retrieve it. I am building reminder app using AlarmManager and I need to save list of future dates. It must be able to retrieve as milliseconds. First I thought to calculate time between today now time and future time and store in shared preference. But that method is not working since I need to use it for AlarmManager.
To save and load accurate date, you could use the long (number) representation of a Date object.
Example:
//getting the current time in milliseconds, and creating a Date object from it:
Date date = new Date(System.currentTimeMillis()); //or simply new Date();
//converting it back to a milliseconds representation:
long millis = date.getTime();
You can use this to save or retrieve Date/Time data from SharedPreferences like this
Save:
SharedPreferences prefs = ...;
prefs.edit().putLong("time", date.getTime()).apply();
Read it back:
Date myDate = new Date(prefs.getLong("time", 0));
Edit
If you want to store the TimeZone additionaly, you could write some helper method for that purpose, something like this (I have not tested them, feel free to correct it, if something is wrong):
public static Date getDate(final SharedPreferences prefs, final String key, final Date defValue) {
if (!prefs.contains(key + "_value") || !prefs.contains(key + "_zone")) {
return defValue;
}
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(prefs.getLong(key + "_value", 0));
calendar.setTimeZone(TimeZone.getTimeZone(prefs.getString(key + "_zone", TimeZone.getDefault().getID())));
return calendar.getTime();
}
public static void putDate(final SharedPreferences prefs, final String key, final Date date, final TimeZone zone) {
prefs.edit().putLong(key + "_value", date.getTime()).apply();
prefs.edit().putString(key + "_zone", zone.getID()).apply();
}
You can do this:
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss", Locale.US);
To save a date:
preferences .edit().putString("mydate", sdf.format(date)).apply();
To retrieve:
try{
Date date = sdf.parse(preferences.getString("myDate", "defaultValue"));
} catch (ParseException e) {
e.printStackTrace();
}
Hope it help.
tl;dr
The modern approach uses java.time classes and ISO 8601 strings.
Reading.
Instant // Represent a moment in UTC with a resolution of nanoseconds.
.ofEpochMilli(
Long.getLong( incomingText )
) // Returns a `Instant` object.
.atZone( // Adjust from UTC to some time zone. Same moment, same point on the timeline, different wall-clock time.
ZoneId.of( "Europe/Paris" )
) // Returns a `ZonedDateTime` object.
Writing.
ZonedDateTime
.of(
LocalDate.of( 2018 , Month.JANUARY , 23 ) ,
LocalTime.of( 15 , 35 ) ,
ZoneId.of( "Europe/Paris" )
) // Returns a `ZonedDateTime` object.
.toInstant() // Returns an `Instant`. Adjust from a time zone to UTC. Same moment, same point on the timeline, different wall-clock time.
.toEpochMilli() // Returns a `long` integer number primitive. Any microseconds or nanoseconds are ignored, of course.
If your alarm manager has not yet been modernized to handle java.time objects, convert between legacy & modern classes using new methods added to the old classes.
java.util.Date d = java.util.Date.from( instant ) ;
…and…
Instant instant = d.toInstant() ;
java.time
The troublesome old date-time classes were supplanted by the java.time classes.
For a moment in UTC, with a resolution of nanoseconds, use Instant.
Instant instant = Instant.now() ; // Capture the current moment in UTC.
You want only milliseconds for your needs, so truncate any microseconds & nanoseconds.
Instant instant = Instant.now().truncatedTo( ChronoUnit.MILLIS ) ;
To determine a moment by date and time-of-day requires a time zone. A time zone is crucial in determining a date. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.
If no time zone is specified, the JVM implicitly applies its current default time zone. That default may change at any moment during runtime(!), so your results may vary. Better to specify your desired/expected time zone explicitly as an argument.
Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( z ) ;
If you want to use the JVM’s current default time zone, ask for it and pass as an argument. If omitted, the JVM’s current default is applied implicitly. Better to be explicit, as the default may be changed at any moment during runtime by any code in any thread of any app within the JVM.
ZoneId z = ZoneId.systemDefault() ; // Get JVM’s current default time zone.
Or specify a date. You may set the month by a number, with sane numbering 1-12 for January-December.
LocalDate ld = LocalDate.of( 1986 , 2 , 23 ) ; // Years use sane direct numbering (1986 means year 1986). Months use sane numbering, 1-12 for January-December.
Or, better, use the Month enum objects pre-defined, one for each month of the year. Tip: Use these Month objects throughout your codebase rather than a mere integer number to make your code more self-documenting, ensure valid values, and provide type-safety.
LocalDate ld = LocalDate.of( 1986 , Month.FEBRUARY , 23 ) ;
Combine with a time-of-day, a LocalTime.
LocalTime lt = LocalTime.of( 14 , 0 ) ;
Wrap it all together as a ZonedDateTime object.
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;
Adjust to UTC by extracting a Instant.
Instant instant = zdt.toInstant() ;
Extract your desired count-of-milliseconds since the epoch reference of first moment of 1970 in UTC. Again, be aware that any micros/nanos in your Instant will be ignored when extracting milliseconds.
long milliseconds = instant.toEpochMilli() ; // Be aware of potential data loss, ignoring any microseconds or nanoseconds.
Read those milliseconds back from storage as text using the Long class.
long milliseconds = Long.getLong( incomingText ) ;
Instant instant = Instant.ofEpochMilli( milliseconds ) ;
To see that moment through the lens of the wall-clock time used by the people of a particular region (a time zone), apply a ZoneId to get a ZonedDateTime.
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;
To generate text representing that value, use DateTimeFormatter.ofLocalizedDateTime to automatically localize.
Tip: Consider writing your date-time values to storage in standard ISO 8601 format rather than as a count-of-milliseconds. The milliseconds cannot be read meaningfully by humans, making debugging & monitoring tricky.
String output = instant.toString() ;
2018-10-05T20:28:48.584Z
Instant instant = Instant.parse( 2018-10-05T20:28:48.584Z ) ;
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.