First, I want to state that I know the Java Calendar class is being supplanted by other libraries that are arguably better. Perhaps I've stumbled upon one of the reasons Calendar has fallen out of favor.
I ran into frustrating behavior in Calendar as it regards to the overlapping hour at the end of daylight savings time.
public void annoying_issue()
{
Calendar midnightPDT = Calendar.getInstance(TimeZone.getTimeZone("US/Pacific"));
midnightPDT.set(Calendar.YEAR, 2021);
midnightPDT.set(Calendar.MONTH, 10);
midnightPDT.set(Calendar.DAY_OF_MONTH, 7);
midnightPDT.set(Calendar.HOUR_OF_DAY, 0);
midnightPDT.set(Calendar.MINUTE, 0);
midnightPDT.set(Calendar.SECOND, 0);
midnightPDT.set(Calendar.MILLISECOND, 0);
Calendar oneAMPDT = Calendar.getInstance(TimeZone.getTimeZone("US/Pacific"));
oneAMPDT.setTimeInMillis(midnightPDT.getTimeInMillis() + (60*60*1000));//this is the easiest way I've found to get to the first 1am hour at DST overlap
System.out.println(new Date(midnightPDT.getTimeInMillis()));//prints the expected "Sun Nov 7 00:00:00 PDT 2021"
System.out.println(new Date(oneAMPDT.getTimeInMillis()));//prints "Sun Nov 7 01:00:00 PDT 2021" also expected
oneAMPDT.clear(Calendar.MINUTE);//minute is already 0 so no change should occur... RIGHT!?
//WRONG!!!!
//The time is now in PST! The millisecond value has increased by 3600000, too!!
System.out.println(new Date(oneAMPDT.getTimeInMillis()));//prints "Sun Nov 7 01:00:00 PST 2021"
}
Following along with the comments you'll see that clearing the MINUTE field in the calendar actually moved it up an hour! The HECK!?
This also occurs when I use oneAMPDT.set(Calendar.MINUTE, 0)
Is this expected behavior? Is there a way to prevent this?
Avoid legacy date-time classes; convert if needed
As you noted, Calendar was supplanted years ago by the java.time classes defined in JSR 310 (unanimously adopted). And as you note there are many reasons to avoid using Calendar & Date etc.
If you must have a Calendar object to interoperate with old code not yet updated to java.time, convert after doing your work in java.time.
java.time
Specify your desired time zone. Note that US/Pacific is merely an alias for the actual time zone, America/Los_Angeles.
ZoneId zLosAngeles = ZoneId.of( "America/Los_Angeles" ) ;
Specify your desired moment.
LocalDate ld = LocalDate.of( 2021 , Month.NOVEMBER , 7 ) ;
In your code, you seem to assume the first moment of the day occurs at 00:00. That is not always the case. Some dates in some time zones may start at another time. So let java.time determine the first moment of the day.
ZonedDateTime firstMomentOfThe7thInLosAngeles = ld.atStartOfDay( zLosAngeles ) ;
firstMomentOfThe7thInLosAngeles.toString(): 2021-11-07T00:00-07:00[America/Los_Angeles]
But then you jumped to another moment, to 1 AM.
ZonedDateTime oneAmOnThe7thLosAngeles = firstMomentOfThe7thInLosAngeles.with( LocalTime.of( 1 , 0 ) ) ;
oneAmOnThe7thLosAngeles.toString(): 2021-11-07T01:00-07:00[America/Los_Angeles]
That time-of-day may or may not exist on that date in that zone. The ZonedDateTime class will adjust if need be.
You used the name midnightPDT for a variable. I suggest avoiding the term midnight as its use confuses date-time handling with out a precise definition. I recommend using the term "first moment of the day" if that is what you mean.
You extract a count of milliseconds since the epoch reference of first moment of 1970 as seen in UTC, 1970-01-01T00:00Z.
Instant firstMomentOfThe7thInLosAngelesAsSeenInUtc = firstMomentOfThe7thInLosAngeles.toInstant() ;
long millisSinceEpoch_FirstMomentOf7thLosAngeles = firstMomentOfThe7thInLosAngelesAsSeenInUtc.toEpochMilli() ;
firstMomentOfThe7thInLosAngelesAsSeenInUtc.toString(): 2021-11-07T07:00:00Z
millisSinceEpoch_FirstMomentOf7thLosAngeles = 1636268400000
And you do the same for our 1 AM moment.
Instant oneAmOnThe7thLosAngelesAsSeenInUtc = oneAmOnThe7thLosAngeles.toInstant() ;
long millisSinceEpoch_OneAmOn7thLosAngeles = oneAmOnThe7thLosAngelesAsSeenInUtc.toEpochMilli() ;
oneAmOnThe7thLosAngelesAsSeenInUtc.toString(): 2021-11-07T08:00:00Z
millisSinceEpoch_OneAmOn7thLosAngeles = 1636272000000
We should see a difference of one hour. An hour = 3,600,000 = 60 * 60 * 1,000.
long diff = ( millisSinceEpoch_OneAmOn7thLosAngeles - millisSinceEpoch_FirstMomentOf7thLosAngeles ); // 3,600,000 = 60 * 60 * 1,000.
diff = 3600000
Cutover
Then you go on to mention the Daylight Saving Time (DST) cutover. The cutover for DST in the United States on that date was 2 AM, not 1 AM. At the moment of 2 AM arriving, the clocks swung back to 1 AM, for a second 1:00-2:00 AM hour.
To get to that point of cutover, let's add an hour.
ZonedDateTime cutover_Addition = oneAmOnThe7thLosAngeles.plusHours( 1 );
cutover_Addition = 2021-11-07T01:00-08:00[America/Los_Angeles]
Notice that the time-of-day shows the same (1 AM), but the offset-from-UTC has changed from being 7 hours behind UTC to now 8 hours behind UTC. There lies the hour difference you seek.
Let's get the count of milliseconds since epoch for this third moment. Before we had first moment of the day (00:00), then the first occurring 1 AM, and now we have the second occurring 1 AM on this “Fall-Back” date of November 7, 2021.
long millisSinceEpoch_Cutover = cutover_Addition.toInstant().toEpochMilli();
1636275600000
Duration.between( firstMomentOfThe7thInLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) = PT2H
Duration.between( oneAmOnThe7thLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) = PT1H
The ZonedDateTime class does offer a pair of methods of use at these moments of cutover: withEarlierOffsetAtOverlap and withLaterOffsetAtOverlap.
ZonedDateTime cutover_OverlapEarlier =
cutover_Addition
.withEarlierOffsetAtOverlap();
ZonedDateTime cutover_OverlapLater =
cutover_Addition
.withLaterOffsetAtOverlap();
cutover_OverlapEarlier = 2021-11-07T01:00-07:00[America/Los_Angeles]
cutover_OverlapLater = 2021-11-07T01:00-08:00[America/Los_Angeles]
Calendar
If you really need a Calendar object, just convert.
Calendar x = GregorianCalendar.from( firstMomentOfThe7thInLosAngeles ) ;
Calendar y = GregorianCalendar.from( oneAmOnThe7thLosAngeles ) ;
Calendar z = GregorianCalendar.from( cutover_Addition );
If you goal is simply struggling with understanding Calendar class behavior, I suggest you stop the masochism. There is no point. Sun, Oracle, and the JCP community all gave up on those terrible legacy date-time classes. I suggest you do the same.
Example code
Pulling together all that code above.
ZoneId zLosAngeles = ZoneId.of( "America/Los_Angeles" );
LocalDate ld = LocalDate.of( 2021 , Month.NOVEMBER , 7 );
ZonedDateTime firstMomentOfThe7thInLosAngeles = ld.atStartOfDay( zLosAngeles );
ZonedDateTime oneAmOnThe7thLosAngeles = firstMomentOfThe7thInLosAngeles.with( LocalTime.of( 1 , 0 ) );
Instant firstMomentOfThe7thInLosAngelesAsSeenInUtc = firstMomentOfThe7thInLosAngeles.toInstant();
long millisSinceEpoch_FirstMomentOf7thLosAngeles = firstMomentOfThe7thInLosAngelesAsSeenInUtc.toEpochMilli();
Instant oneAmOnThe7thLosAngelesAsSeenInUtc = oneAmOnThe7thLosAngeles.toInstant();
long millisSinceEpoch_OneAmOn7thLosAngeles = oneAmOnThe7thLosAngelesAsSeenInUtc.toEpochMilli();
long diff = ( millisSinceEpoch_OneAmOn7thLosAngeles - millisSinceEpoch_FirstMomentOf7thLosAngeles ); // 3,600,000 = 60 * 60 * 1,000.
ZonedDateTime cutover_Addition = oneAmOnThe7thLosAngeles.plusHours( 1 );
long millisSinceEpoch_Cutover = cutover_Addition.toInstant().toEpochMilli();
ZonedDateTime cutover_OverlapEarlier =
cutover_Addition
.withEarlierOffsetAtOverlap();
ZonedDateTime cutover_OverlapLater =
cutover_Addition
.withLaterOffsetAtOverlap();
Convert to legacy classes, if need be.
Calendar x = GregorianCalendar.from( firstMomentOfThe7thInLosAngeles );
Calendar y = GregorianCalendar.from( oneAmOnThe7thLosAngeles );
Calendar z = GregorianCalendar.from( cutover_Addition );
Dump to console.
System.out.println( "firstMomentOfThe7thInLosAngeles = " + firstMomentOfThe7thInLosAngeles );
System.out.println( "oneAmOnThe7thLosAngeles = " + oneAmOnThe7thLosAngeles );
System.out.println( "firstMomentOfThe7thInLosAngelesAsSeenInUtc = " + firstMomentOfThe7thInLosAngelesAsSeenInUtc );
System.out.println( "millisSinceEpoch_FirstMomentOf7thLosAngeles = " + millisSinceEpoch_FirstMomentOf7thLosAngeles );
System.out.println( "oneAmOnThe7thLosAngelesAsSeenInUtc = " + oneAmOnThe7thLosAngelesAsSeenInUtc );
System.out.println( "millisSinceEpoch_OneAmOn7thLosAngeles = " + millisSinceEpoch_OneAmOn7thLosAngeles );
System.out.println( "diff = " + diff );
System.out.println( "x = " + x );
System.out.println( "y = " + y );
System.out.println( "z = " + z );
System.out.println( "cutover_Addition = " + cutover_Addition );
System.out.println( "millisSinceEpoch_Cutover = " + millisSinceEpoch_Cutover );
System.out.println( "Duration.between( firstMomentOfThe7thInLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) = " + Duration.between( firstMomentOfThe7thInLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) );
System.out.println( "Duration.between( oneAmOnThe7thLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) = " + Duration.between( oneAmOnThe7thLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) );
System.out.println( "cutover_OverlapEarlier = " + cutover_OverlapEarlier );
System.out.println( "cutover_OverlapLater = " + cutover_OverlapLater );
When run.
firstMomentOfThe7thInLosAngeles = 2021-11-07T00:00-07:00[America/Los_Angeles]
oneAmOnThe7thLosAngeles = 2021-11-07T01:00-07:00[America/Los_Angeles]
firstMomentOfThe7thInLosAngelesAsSeenInUtc = 2021-11-07T07:00:00Z
millisSinceEpoch_FirstMomentOf7thLosAngeles = 1636268400000
oneAmOnThe7thLosAngelesAsSeenInUtc = 2021-11-07T08:00:00Z
millisSinceEpoch_OneAmOn7thLosAngeles = 1636272000000
diff = 3600000
x = java.util.GregorianCalendar[time=1636268400000,areFieldsSet=true,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=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2021,MONTH=10,WEEK_OF_YEAR=44,WEEK_OF_MONTH=1,DAY_OF_MONTH=7,DAY_OF_YEAR=311,DAY_OF_WEEK=1,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]
y = java.util.GregorianCalendar[time=1636272000000,areFieldsSet=true,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=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2021,MONTH=10,WEEK_OF_YEAR=44,WEEK_OF_MONTH=1,DAY_OF_MONTH=7,DAY_OF_YEAR=311,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=1,HOUR_OF_DAY=1,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]
z = java.util.GregorianCalendar[time=1636275600000,areFieldsSet=true,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=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2021,MONTH=10,WEEK_OF_YEAR=44,WEEK_OF_MONTH=1,DAY_OF_MONTH=7,DAY_OF_YEAR=311,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=1,HOUR_OF_DAY=1,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-28800000,DST_OFFSET=0]
cutover_Addition = 2021-11-07T01:00-08:00[America/Los_Angeles]
millisSinceEpoch_Cutover = 1636275600000
Duration.between( firstMomentOfThe7thInLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) = PT2H
Duration.between( oneAmOnThe7thLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) = PT1H
cutover_OverlapEarlier = 2021-11-07T01:00-07:00[America/Los_Angeles]
cutover_OverlapLater = 2021-11-07T01:00-08:00[America/Los_Angeles]
java.time
Is this expected behavior? No. I consider it a bug.
Is there a way to prevent this? Yes, the way you already mentioned or at least implied: use ZonedDateTime instead of Calendar. Basil Bourque has said it already. As a modest supplement I wanted to show the full round-trip from Calendar to ZonedDateTime, setting minute to 0 and converting back to Calendar. In case you need this for interoperability with your legacy code.
GregorianCalendar oneAmPdt = new GregorianCalendar(TimeZone.getTimeZone(ZoneId.of("America/Los_Angeles")));
oneAmPdt.clear();
oneAmPdt.set(2021, Calendar.NOVEMBER, 7, 0, 0);
oneAmPdt.add(Calendar.HOUR_OF_DAY, 1);
System.out.println(oneAmPdt.getTime());
ZonedDateTime zdt = oneAmPdt.toZonedDateTime();
// Minute is already 0 so no change should occur... RIGHT!?
zdt = zdt.withMinute(0);
oneAmPdt = GregorianCalendar.from(zdt);
System.out.println(oneAmPdt.getTime());
Output:
Sun Nov 07 01:00:00 PDT 2021
Sun Nov 07 01:00:00 PDT 2021
But I used GregorianCalendar, not Calendar? So did you. GregorianCalendar is the subclass of Calendar that you got from Calendar.getIntance(). In some environments you would have got a different subclass reflecting the calendar system in use there, and your initial calls to set would not have given you your expected result. You want a GregorianCalendar in this case (if you cannot have a ZonedDateTime from the outset).
When modifying our old code I would likely do it in the above way even if it wasn’t for circumventing a bug in the old Calendar or GregorianCalendar class. It’s one small step in a long-running transition to java.time.
I have the string date "3.9.1991". And I want to obtain how many years have passed since the date. For example 23. How can I achieve it using Calendar or Date?
EDIT:
I have been trying this:
private String parseVkBirthday(String birthdayString) {
// 3.9.1991
SimpleDateFormat formatter = new SimpleDateFormat("d.M.yyyy");
String formattedDate = null;
Calendar date = Calendar.getInstance();
int year = 0;
try {
Date currentDate = new Date();
Date birthdayDate = formatter.parse(birthdayString);
Log.d(LOG_TAG, "year1 = " + currentDate.getTime() + " year2 = " + birthdayDate.getTime());
long diff = currentDate.getTime() - birthdayDate.getTime();
birthdayDate.setTime(diff);
date.setTime(birthdayDate);
year = date.get(Calendar.YEAR);
} catch (ParseException e) {
e.printStackTrace();
}
return "" + year;
}
But it returns me 1993.
Answer:
There are while 3 ways for solving this problem:
1) As codeaholicguy answered, we can use Joda-Time library(what I prefer for Android).
2) As Basil Bourque answered, we can use ThreeTen-Backport library for using java.time classes from java 8.
3) And we can use java 8 and classes from java.time.
Thanks to everyone.
Use SimpleDateFormat and Period of Joda-Time library, example below:
String pattern = "dd.MM.yyyy";
SimpleDateFormat format = new SimpleDateFormat(pattern);
Date date = format.parse("3.9.1991");
System.out.println(date);
Period period = new Period(date.getTime(), (new Date()).getTime());
System.out.println(period.getYears());
String fullDate="3.9.1991";
String[] splitDate=fullDate.split(".");
int year=Integer.parseInt(splitDate[2]);
int currentYear = Calendar.getInstance().get(Calendar.YEAR);
int passedYears=currentYear-year;
Calendar.YEAR can be used to add or subtract year from current date in the same fashion we added days and month into date.
http://javarevisited.blogspot.in/2012/12/how-to-add-subtract-days-months-years-to-date-time-java.html
sample program:
import java.util.Calendar;
public class Years {
public static void main(String[] args) {
//create Calendar instance
Calendar now = Calendar.getInstance();
System.out.println("Current date : " + (now.get(Calendar.MONTH) + 1)
+ "-"
+ now.get(Calendar.DATE)
+ "-"
+ now.get(Calendar.YEAR));
//add year to current date using Calendar.add method
now.add(Calendar.YEAR,1);
System.out.println("date after one year : " + (now.get(Calendar.MONTH) + 1)
+ "-"
+ now.get(Calendar.DATE)
+ "-"
+ now.get(Calendar.YEAR));
//substract year from current date
now =Calendar.getInstance();
now.add(Calendar.YEAR,-100);
System.out.println("date before 100 years : " + (now.get(Calendar.MONTH) + 1)
+ "-"
+ now.get(Calendar.DATE)
+ "-"
+ now.get(Calendar.YEAR));
}
}
http://forgetcode.com/Java/1568-Adding-or-Subtracting-Years-to-Current-Date#
Based on example code by xrcwrn with the Joda-Time 2.8 library:
// get the current year with #xrcwm's code
Calendar mydate = new GregorianCalendar();
String mystring = "3.9.1991";
Date thedate = new SimpleDateFormat("d.m.yyyy", Locale.ENGLISH).parse(mystring);
DateTime myDateTime = new DateTime(thedate.getTime()); // joda DateTime object
// get he current date
DateTime currentDateTime = new DateTime();
// get the years value
long years = Years.between(currentDateTime, myDateTime).getYears()
The code above should give you the correct value. Mind you, this code may have some syntax errors.
As a side note, Java 8 has a time package which seems to provide more of the same functionality.
java.time
The new java.time package in Java 8 and later supplants the old java.util.Date/.Calendar & SimpleTextFormat classes.
First parse the string using new DateTimeFormatter class. Do not use SimpleTextFormat. And read the doc as there may be subtle differences in the symbol codes between the old and new classes.
Get today's date, to calculate elapsed years. Note that we need a time zone. Time zone is crucial in determining a date. A new day dawns earlier in Paris, for example, than it does in Montréal.
The Period class considers a span of time as a number of years, months and days not tied to any points on the timeline.
The between method uses the "Half-Open" approach common to date-time handling. The beginning is inclusive while the ending is exclusive.
The default formatting of java.time follows the ISO 8601 standard. Apply formatter if you wish a different string representation of your date-time values.
String input = "3.9.1991" ;
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d.M.yyyy") ;
LocalDate then = LocalDate.parse( input, formatter ) ;
ZoneId zone = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( zone ) ; // Specify time zone to get your locality’s current date.
Period period = Period.between( then , today ) ;
int years = period.getYears() ;
System.out.println( "Between " + then + " and " + today + " is " + years + " years.");
Between 1991-09-03 and 2015-07-09 is 23 years.
Joda-Time
Android currently lacks Java 8 features. So you cannot use java.time. Unless perhaps the ThreeTen-Backport project (a) supports the classes used in the above example and (b) works on Android (I do not know about either).
Alternatively, you can use Joda-Time, the third-party library that inspired java.time. The Joda-Time code version of the above code example would be very similar. In this case, java.time and Joda-Time parallel one another with similar classes.
Calendar mydate = new GregorianCalendar();
String mystring = "3.9.1991";
Date thedate = new SimpleDateFormat("d.m.yyyy", Locale.ENGLISH).parse(mystring);
mydate.setTime(thedate);
//breakdown
System.out.println("year -> "+mydate.get(Calendar.YEAR));
Reference
I have a Date Object which I need to convert to the logged in user's timezone. The problem is that the timezone is represented in our DB simply as a String value of GMT plus or minus the offset in hours. So for example "GMT" or "GMT-5" for New york time or "GMT+5".
How can I convert my Date Object to the User's time when all I have are String like "GMT-3" or "GMT+5"?
Thanks in advance for any help.
An example should help, but it seems a 1 character ISO 8601 time zone:
String myDate="2001-07-04T12:08:56GMT-3";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'GMT'X");
if (myDate.indexOf("GMT-") >= myDate.length() -1 -4) {
myDate = myDate.replace("-","-0");
}
if (myDate.indexOf("GMT+") >= myDate.length() -1 -4) {
myDate = myDate.replace("+","+0");
}
System.out.println(format.parse(myDate));
it should work.
the yyyy-MM-dd'T'HH:mm:ss'GMT'X is compliant with iso8601 time zone
myDate = myDate.replace("-","-0"); adjusts the date to your format
Offset ≠ Time Zone
As Jon Skeet said in comment, a time zone is more than just an offset from UTC/GMT. Storing the offset hours (and minutes) is a less-than-optimal strategy for handling date-time in your database/storage.
Joda-Time
The java.util.Date & java.util.Calendar classes are notoriously troublesome. Avoid them. Use Joda-Time. Or, in Java 8, use the new java.time.* package, defined by JSR 310, and inspired by Joda-Time but re-architected.
We can create a DateTimeZone to represent the offset, but as noted this does not make a complete time zone logically.
We can pass a java.util.Date object directly to a Joda-Time DateTime constructor. Along with that we pass a DateTimeZone object. To go the other direction of conversion, call toDate on a DateTime object.
java.util.Date date = new java.util.Date(); // Retrieved from elsewhere. Faked here.
String offsetInput = "GMT-5";
int offsetHours = 0, offsetMinutes = 0;
offsetInput = offsetInput.replace( "GMT", "" ); // Delete 'GMT' characters.
String[] parts = offsetInput.split(":"); // About splitting a string: http://stackoverflow.com/q/3481828/642706
// Handle results of split.
if( parts.length == 0 ) {
// Add some error handling here
}
if ( parts.length >= 1 ) {
offsetHours = Integer.parseInt( parts[0] ); // Retrieve text of first number (zero-based index counting).
}
if ( parts.length >= 2 ) {
offsetMinutes = Integer.parseInt( parts[1] ); // Retrieve text of second number (zero-based index counting).
}
if( parts.length >= 3 ) {
// Add some error handling here
}
DateTimeZone partialTimeZoneWithOnlyOffset = DateTimeZone.forOffsetHoursMinutes( offsetHours, offsetMinutes );
DateTime dateTime = new DateTime( date, partialTimeZoneWithOnlyOffset );
Dump to console…
System.out.println( "date: " + date ); // BEWARE: JVM's default time zone applied in the implicit call to "toString" of a Date. Very misleading.
System.out.println( "partialTimeZoneWithOnlyOffset: " + partialTimeZoneWithOnlyOffset );
System.out.println( "dateTime: " + dateTime );
System.out.println( "dateTime with alternate formatting: " + DateTimeFormat.forStyle( "FF" ).withLocale( Locale.US ).print( dateTime ) );
When run…
date: Sat Feb 08 22:40:57 PST 2014
partialTimeZoneWithOnlyOffset: -05:00
dateTime: 2014-02-09T01:40:57.810-05:00
dateTime with alternate formatting: Sunday, February 9, 2014 1:40:57 AM -05:00
Below is my code which checks the date which is stored in database with the current system date and calculates the days and if that days is lesser than the 180 days it will print something else print nothing,this code works great in an normal java program(with out using swings concept) if it is used with the swing program i changed the sql query to check get the date from the database based on the department and staff names which is entered in the text fields,i coded this code inside an jbutton,in the output it just prints the current system date but not calculates the days between the selected date and the current system dates,friends this is the problem am facing kindly need your help friends....thanks in advance..
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
try {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/leave", "root", "");
Statement stm = conn.createStatement();
ResultSet rs = stm.executeQuery("select * from staff where depmt='" + txt1 + "' AND staffs='" + txt2 + "'");
Calendar javaCalendar = null;
String currentDate = "";
javaCalendar = Calendar.getInstance();
currentDate = javaCalendar.get(Calendar.DATE) + "/" + (javaCalendar.get(Calendar.MONTH) + 1) + "/" + javaCalendar.get(Calendar.YEAR);
int cdate = javaCalendar.get(Calendar.DATE);
int cmonth = (javaCalendar.get(Calendar.MONTH) + 1);
int cyear = javaCalendar.get(Calendar.YEAR);
int z = 0;
int date = 0, month = 0, year = 0;
System.out.println("Current Date\t" + currentDate);
System.out.println("\n");
while (rs.next()) {
date = rs.getInt(3);
month = rs.getInt(4);
year = rs.getInt(5);
System.out.println("Random Date\t" + date + "/" + month + "/" + year + "\n");
int d = (date - cdate);
int m = month - cmonth;
int y = year - cyear;
int d1 = java.lang.Math.abs(d);
int d2 = java.lang.Math.abs(m);
int d3 = java.lang.Math.abs(y);
z = d1 + (d2 * 30) + (d3 * 365);
if (z >= 180) {
System.out.println("something");
0
} else {
System.out.println("nothing");
}
}
} catch (Exception e) {
System.out.println(e.getMessage());
//e.printStackTrace();
}
// TODO add your handling code here:
}
You should really use prepared statements cause this way your query is prone to sql injections.
Date formatter insted of concating string for currentdate
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
String formattedDate = formatter.format(todaysDate);
Also it seems like your not closeing the connection that may be another issue.
Is there any reason for storeing the date in 3 separate columns?
Your algorithm to calculate the day difference between two dates is broken. It does not take in account different month lengths or leap years.
Unfortunately Java Calendar does not offer this feature at all. So either you apply your own homegrown algorithm (not easy, but in web there are some sources how to map a gregorian date to epoch days) or you use JodaTime like this way:
LocalDate db = new LocalDate(year, month, date);
int days = Days.daysBetween(db, LocalDate.now()).getDays();
Note that the result will be negative if db date is in the future. After all you can greatly shorten your code and abandon all Calendar stuff which is very bad for calculations of durations.
try this:
DateFormat df = new SimpleDateFormat("dd/MM/yyyy");
Date date1 = df.parse("14/02/2014");
Date date2 = df.parse("08/03/2014");
int days = Days.daysBetween(date1, date2).getDays();
Try this, by changing return value from millisecond to day.
public static int daysBetween(Date dateFrom, Date dateTo){
return (int)( (dateTo.getTime() - dateFrom.getTime()) / (1000 * 60 * 60 * 24));
}
Start Of Day
If you start with a mid-afternoon date-time object, go back 180 days by calculating seconds * minutes * hours * 180, you'll end up excluding the date-times earlier in that day 180 ago whereas I suppose you would want to include them. You should pay attention to when the day begins.
Time Zone
Both the question and other answers ignore the issue of time zone. Time zone defines the beginning of a day. Given the point about start of day (above), time zone is a related component.
Avoid java.util.Date & .Calendar
The java.util.Date and .Calendar classes bundled with Java are notoriously troublesome. Avoid them. Instead use either Joda-Time or the new java.time package in Java 8.
Joda-Time
Here is some example code using Joda-Time 2.3.
Note that while a Joda-Time DateTime object is similar to a java.util.Date, a DateTime does truly know its own assigned time zone.
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Paris" );
DateTime dateTimeInQuestion = new DateTime( 2013, 6, 5, 4, 3, 2, timeZone );
DateTime now = new DateTime( timeZone );
DateTime hundredEightyDaysAgo = now.minusDays( 180 ).withTimeAtStartOfDay();
boolean isExpired = dateTimeInQuestion.isBefore( hundredEightyDaysAgo );
Dump to console…
System.out.println( "dateTimeInQuestion: " + dateTimeInQuestion );
System.out.println( "now: " + now );
System.out.println( "hundredEightyDaysAgo: " + hundredEightyDaysAgo );
System.out.println( "isExpired: " + isExpired );
When run…
dateTimeInQuestion: 2013-06-05T04:03:02.000+02:00
now: 2014-03-10T07:34:26.937+01:00
hundredEightyDaysAgo: 2013-09-11T00:00:00.000+02:00
isExpired: true
If possible I would prefer a joda or non-joda solution for the scenario below
Lets say if my week starts on 02/05/2012 and the given current date is 02/22/2011. I need to calculate the week start and end date for the given current date. So my solution should have the week start as 02/19 and week ends at 02/25.
For simplicity, I have set my week start here as 02/05/2011 but it could be any day potentially and my week always has 7 days.
My existing code is below but doesnt seem to work as expected.
public Interval getWeekInterval(Date calendarStartDate, Date date)
{
Calendar sDate = Calendar.getInstance();
sDate.setTime(getMidnightDate(calendarStartDate));
Calendar eDate = Calendar.getInstance();
eDate.setTime(date);
Calendar weekStartDate = (Calendar) sDate.clone();
logger.debug("Date:" + sDate.getTime());
while (sDate.before(eDate)) {
weekStartDate = sDate;
sDate.add(Calendar.DAY_OF_WEEK_IN_MONTH, 1);
}
return new Interval(weekStartDate.getTime(), sDate.getTime());
}
Defining A Week
If you are using date-time objects, you should define a week as up to but not including the first moment of the day after the end of week. As seen in this diagram.
This approach is known as Half-Open. This approach is commonly used for working with spans of time.
The reason is because, logically, that last moment of the day before the new day is infinitely divisible as a fraction of a second. You may think that using ".999" would handle that for milliseconds, but then you'd mistaken when writing for the new java.time.* classes in Java 8 that have nanosecond resolution rather than millisecond. You may think think that using ".999999999" would handle that case, but then you’d be mistaken when handling date-time values from many databases such as Postgres that use microsecond resolution, ".999999".
In the third-party open-source Joda-Time library, this Half-Open logic is how its Interval class works. The beginning is inclusive and the ending is exclusive. This works out nicely. Similarly, calling plusWeeks(1) on a DateTime to add a week to the first moment of a day gives you the first moment of the 8th day later (see example below).
Time Zone
The question and other answers ignores the issue of time zone. If you do not specify, you'll be getting the default time zone. Usually better to specify a time zone, using a proper time zone name (not 3-letter code).
Joda-Time
Avoid the java.util.Date & Calendar classes bundled with Java. They are notoriously troublesome.
Here is some example code using Joda-Time 2.3.
CAVEAT: I have not tested of of the below code thoroughly. Just my first take, a rough draft. May well be flawed.
Standard Week (Monday-Sunday)
The Joda-Time library is built around the ISO 8601 standard. That standard defines the first day of the week as Monday, last day as Sunday.
If that meets your definition of a week, then getting the beginning and ending is easy.
UPDATE As an alternative to the discussion below, see this very clever and very simple one-liner solution by SpaceTrucker.
Simply forcing the day-of-week works because Joda-Time assumes you want:
Monday to be before (or same as) today.
Sunday to be after (or same as) today.
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Paris" );
DateTime now = new DateTime( timeZone );
DateTime weekStart = now.withDayOfWeek( DateTimeConstants.MONDAY ).withTimeAtStartOfDay();
DateTime weekEnd = now.withDayOfWeek(DateTimeConstants.SUNDAY).plusDays( 1 ).withTimeAtStartOfDay();
Interval week = new Interval( weekStart, weekEnd );
Dump to console…
System.out.println( "now: " + now );
System.out.println( "weekStart: " + weekStart );
System.out.println( "weekEnd: " + weekEnd );
System.out.println( "week: " + week );
When run…
now: 2014-01-24T06:29:23.043+01:00
weekStart: 2014-01-20T00:00:00.000+01:00
weekEnd: 2014-01-27T00:00:00.000+01:00
week: 2014-01-20T00:00:00.000+01:00/2014-01-27T00:00:00.000+01:00
To see if a date-time lands inside that interval, call the contains method.
boolean weekContainsDate = week.contains( now );
Non-Standard Week
If that does not meet your definition of a week, you a twist on that code.
DateTimeZone timeZone = DateTimeZone.forID( "America/New_York" );
DateTime now = new DateTime( timeZone );
DateTime weekStart = now.withDayOfWeek( DateTimeConstants.SUNDAY ).withTimeAtStartOfDay();
if ( now.isBefore( weekStart )) {
// If we got next Sunday, go back one week to last Sunday.
weekStart = weekStart.minusWeeks( 1 );
}
DateTime weekEnd = weekStart.plusWeeks( 1 );
Interval week = new Interval( weekStart, weekEnd );
Dump to console…
System.out.println( "now: " + now );
System.out.println( "weekStart: " + weekStart );
System.out.println( "weekEnd: " + weekEnd );
System.out.println( "week: " + week );
When run…
now: 2014-01-24T00:54:27.092-05:00
weekStart: 2014-01-19T00:00:00.000-05:00
weekEnd: 2014-01-26T00:00:00.000-05:00
week: 2014-01-19T00:00:00.000-05:00/2014-01-26T00:00:00.000-05:00
First day of week depends on the country.
What makes the calculation fragile, is that one may break the year boundary, and the week number (Calendar.WEEK_OF_YEAR). The following would do:
Calendar currentDate = Calendar.getInstance(Locale.US);
int firstDayOfWeek = currentDate.getFirstDayOfWeek();
Calendar startDate = Calendar.getInstance(Locale.US);
startDate.setTime(currentDate.getTime());
//while (startDate.get(Calendar.DAY_OF_WEEK) != firstDayOfWeek) {
// startDate.add(Calendar.DATE, -1);
//}
int days = (startDate.get(Calendar.DAY_OF_WEEK) + 7 - firstDayOfWeek) % 7;
startDate.add(Calendar.DATE, -days);
Calendar endDate = Calendar.getInstance(Locale.US);
endDate.setTime(startDate.getTime());
endDate.add(Calendar.DATE, 6);
One bug in Calendar breaks your code, clone, seems to simply give the identical object, hence at the end you have identical dates. (Java 7 at least).
DateTime sDateTime = new DateTime(startDate); // My calendar start date
DateTime eDateTime = new DateTime(date); // the date for the week to be determined
Interval interval = new Interval(sDateTime, sDateTime.plusWeeks(1));
while(!interval.contains(eDateTime))
{
interval = new Interval(interval.getEnd(), interval.getEnd().plusWeeks(1));
}
return interval;
Try this (pseudo-code):
// How many days gone after reference date (a known week-start date)
daysGone = today - referenceDate;
// A new week starts after each 7 days
dayOfWeek = daysGone % 7;
// Now, we know today is which day of the week.
// We can find start & end days of this week with ease
weekStart = today - dayOfWeek;
weekEnd = weekStart + 6;
Now, we can shorten all of this to two lines:
weekStart = today - ((today - referenceDate) % 7);
weekEnd = weekStart + 6;
Note that we subtracted date values like integers to show algorithm. You have to write your java code properly.