I have a variable containing the days since the epoch reference date of 1970-01-01 for a certain date.
Does someone know the way to convert this variable to a java.util.Calendar?
Use the java.time classes in Java 8 and later. In one line:
LocalDate date = LocalDate.ofEpochDay(1000);
Calling ofEpochDay(long epochDay) obtains an instance of LocalDate from the epoch day count.
The following should work:
Calendar c = new GregorianCalendar();
c.setTime(new Date(0));
c.add(Calendar.DAY_OF_YEAR, 1000);
System.err.println(c.getTime());
A note regarding time zones:
A new GregorianCalendar instance is created using the default time zone of the system the program is running on. Since Epoch is relative to UTC (GMT in Java) any time zone different from UTC must be handled with care. The following program illustrates the problem:
TimeZone.setDefault(TimeZone.getTimeZone("GMT-1"));
Calendar c = new GregorianCalendar();
c.setTimeInMillis(0);
System.err.println(c.getTime());
System.err.println(c.get(Calendar.DAY_OF_YEAR));
c.add(Calendar.DAY_OF_YEAR, 1);
System.err.println(c.getTime());
System.err.println(c.get(Calendar.DAY_OF_YEAR));
This prints
Wed Dec 31 23:00:00 GMT-01:00 1969
365
Thu Jan 01 23:00:00 GMT-01:00 1970
1
This demonstrates that it is not enough to use e.g. c.get(Calendar.DAY_OF_YEAR). In this case one must always take into account what time of day it is. This can be avoided by using GMT explicitly when creating the GregorianCalendar: new GregorianCalendar(TimeZone.getTimeZone("GMT")). If the calendar is created such, the output is:
Wed Dec 31 23:00:00 GMT-01:00 1969
1
Thu Jan 01 23:00:00 GMT-01:00 1970
2
Now the calendar returns useful values. The reason why the Date returned by c.getTime() is still "off" is that the toString() method uses the default TimeZone to build the string. At the top we set this to GMT-1 so everything is normal.
Calendar cal = new GregorianCalendar();
cal.setTimeInMillis(0);
cal.add(Calendar.DAY_OF_MONTH, daysSinceEpoch);
Related
Consider the following program
Date date1 = new Date(-124304227239000L);
Date date2 = new Date(0);
System.out.println(date1 + " vs. " + date2);
The result (at least with Java 8 on my computer, and with Java 11 on a different computer):
Sun Jan 01 16:59:21 CET 1970 vs. Thu Jan 01 01:00:00 CET 1970
This seems strange because following the documentation (https://docs.oracle.com/javase/8/docs/api/java/util/Date.html#Date-long-) , negative values as parameter for Date indicate dates before 1970. Instead, I get a Sunday instead of Thursday, but still 1970.
Can anybody explain this to me?
The value you've provided is around 1969/1970 BC, depending on whether you do a Gregorian/Julian cutover or not. Date.toString(), aside from all its other problems, doesn't bother to mention the era.
If you use Instant with the same value, it's clearer:
Instant instant = Instant.ofEpochMilli(-124304227239000L);
System.out.println(instant);
Output:
-1970-12-15T15:59:21Z
I'd draw the following conclusions from this:
When using values in the far past, there are lots of considerations to bear in mind, including textual representation and calendar system
Avoid java.util.Date as far as you can
Simple question why the result is like the following for this code:
Calendar cal2 = Calendar.getInstance();
cal2.set(Calendar.HOUR, 12);
cal2.set(Calendar.AM_PM, Calendar.PM);
System.out.println(cal2.getTime().toString()); // Wed Jan 13 00:11:08 EET 2021
cal2.set(Calendar.AM_PM, Calendar.PM);
System.out.println(cal2.getTime().toString()); // Wed Jan 13 12:11:08 EET 2021
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR, 12);
cal.set(Calendar.AM_PM, Calendar.AM);
System.out.println(cal.getTime().toString()); // Tue Jan 12 12:11:08 EET 2021
cal.set(Calendar.AM_PM, Calendar.AM);
System.out.println(cal.getTime().toString()); // Tue Jan 12 00:11:08 EET 2021
The first looks like it is 12 at midnight not afternoon.
The third one looks like it is 12 afternoon, not midnight
Why setting calendar AM or PM multiple times change the result?
How to set the time correctly?
Calendar is very confusing
You are so far from the first being confused about how the Calendar class works. Fortunately the class is also long outdated. You should not use it.
Why setting calendar AM or PM multiple times change the result? …
To answer your question as asked, Andi80 is correct in the other answer and the comments to it: HOUR goes from 0 through 11. The documentation says about HOUR:
Field number for get and set indicating the hour of the morning or
afternoon. HOUR is used for the 12-hour clock (0 - 11). Noon and
midnight are represented by 0, not by 12. E.g., at 10:04:15.250 PM the
HOUR is 10.
When you first set hour to 12 and AM/PM to PM, one should have expected an exception because the hour value is out of range. But no, a Calendar object with default settings doesn’t give you that. Instead it sets the time to 0 AM the following day; Jan 13 when you ran the code on Jan 12. By Calendar logic hour 12 is the hour that comes after hour 11.
When you set PM again, Calendar takes off from the time you had already got, which is in AM, and changes it into PM, so you get 12:11:08, still on Jan 13, the following day.
Why does it calculate the time twice? Not once and not three times when you do three calls to set()? It’s another confusing trait of Calendar. It calculates the time when you call getTime() (and some designated other methods). At that point it picks up all the changes from the calls to set() up to that point and combines them to the best of its abilities, discarding some if there are conflicts, using rules that no person in their right mind will want to understand.
The case for AM is similar, so I leave the details to the reader.
java.time
… How to set the time correctly?
I recommend that you use java.time, the modern Java date and time API, for your time work. If you just want 12 noon or 12 midnight, they are built in as constants:
LocalTime t12Noon = LocalTime.NOON;
System.out.println(t12Noon);
LocalTime t12Midnight = LocalTime.MIDNIGHT;
System.out.println(t12Midnight);
Output is:
12:00
00:00
A LocalTime is a time of day without a date.
If you have already got a time and only want to adjust the hour and AM/PM, use with():
LocalTime t12Noon = LocalTime.now(ZoneId.systemDefault())
.with(ChronoField.CLOCK_HOUR_OF_AMPM, 12)
.with(ChronoField.AMPM_OF_DAY, 1); // 1 = PM
System.out.println(t12Noon);
LocalTime t12Midnight = LocalTime.now(ZoneId.systemDefault())
.with(ChronoField.CLOCK_HOUR_OF_AMPM, 12)
.with(ChronoField.AMPM_OF_DAY, 0); // 0 = AM
System.out.println(t12Midnight);
12:47:00.665155
00:47:00.669248
If you need the date too, use ZonedDateTime or another appropriate class. All of the date-time classes of java.time that include time of day have the same with method, so the code will be the same.
If you indispensably need a Calendar object for a legacy API that you cannot afford to upgrade to java.time just now, use a ZonedDateTIme from java.time for your time math. Then use GregorianCalendar.from(ZoendDateTIme) for the conversion to a Calendar object.
Links
Documentation of Calendar.HOUR
Oracle tutorial: Date Time explaining how to use java.time.
Calendar.HOUR takes inputs in range 0-11. It will wrap around the 12 to a 0.
Use Calendar.HOUR_OF_DAY instead to use values from 0-23.
I have an application which I create dates that a user can select to an appointment. If a user start to work at 9, and an appointment takes 2 hours, I create dates at 9, 11, 13... until a limit, of course. And then I change the day and start again.
This is the code for doing this:
public List<Agenda> createListOfDates(Calendar initial, Calendar end,
int appointmentDuration, int lunchTimeDuration, int lunchTimeStart) {
List<Agenda> agendaList = new ArrayList<Agenda>();
Agenda agenda = new Agenda();
agenda.setWorkingHour(initial.getTime());
agendaList.add(agenda);
while (true) {
initial.add(Calendar.HOUR_OF_DAY, appointmentDuration);
// Logger.error("" + initial.getTime());
if (initial.getTime().after(end.getTime())) {
break;
} else if (initial.get(Calendar.HOUR_OF_DAY) == lunchTimeStart
&& initial.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY
) {
initial.add(Calendar.HOUR_OF_DAY, lunchTimeDuration);
agenda = new Agenda();
agenda.setWorkingHour(initial.getTime());
agendaList.add(agenda);
} else {
agenda = new Agenda();
agenda.setWorkingHour(initial.getTime());
agendaList.add(agenda);
}
}
for(Agenda agendaX : agendaList){
Logger.info("" + agendaX.getWorkingHour());
}
return agendaList;
}
I am working with the "America/Sao_Paulo" timezone to create these dates. I set the variables "initial" and "end" as "America/Sao_Paulo". My system timezone is "GMT", and that is ok, because I want to save these dates in GMT in the database. When I print the dates in last "for", magically it is already converted from "America/Sao_Paulo" to "GMT" and it is printing right. The strange thing is that from a certain date, it changes the time zone. Example of prints:
Sat Mar 30 12:00:00 GMT 2019
Sat Mar 30 14:00:00 GMT 2019
Sat Mar 30 16:00:00 GMT 2019
Sat Mar 30 18:00:00 GMT 2019
Mon Apr 01 13:00:00 BST 2019
Mon Apr 01 15:00:00 BST 2019
Mon Apr 01 18:00:00 BST 2019
Mon Apr 01 20:00:00 BST 2019
Mon Apr 01 22:00:00 BST 2019
While is in GMT, it is right, but I can't understand this BST. Can it be because it's too much in the future? It always starts on April.
Your system time isn’t GMT, it’s Europe/London (or something similar). In March London time coincides with GMT. Not in April. That’s why.
getWorkingHour() returns an instance of Date (another poorly designed and long outdated class, but let that be a different story for now). When you append it to the empty string, Date.toString is implicitly called and builds the string using your system time zone. During standard time it prints GMT as time zone abbreviation. Summer time (DST) begins in London on the last Sunday of March, in this case March 31. So in April Date.toString on your JVM uses British Summer Time and its abbreviation, BST for printing the time.
The good solution involves two changes:
Don’t rely on the JVM’s default time zone. It can be changed at any time from another part of your program or another program running in the same JVM, so is too fragile. Instead give explicit time zone to your date-time operations.
Skip the old date-time classes Calendar and Date and instead use java.time, the modern Java date and time API. It is so much nicer to work with and gives much clearer code, not least when it comes to conversions between time zones.
Instead of Calendar use ZonedDateTime. Depending on the capabilities of your JDBC driver, convert it to either Instant or OffsetDateTime in UTC for saving to the database.
To create a ZonedDateTime, one option is to use one of its of methods (there are several):
ZonedDateTime initial = ZonedDateTime.of(2019, 3, 10, 9, 0, 0, 0, ZoneId.of("America/Sao_Paulo"));
This creates a date-time of March 10, 2019 at 09:00 in São Paolo. To add 2 hours to it:
int appointmentDuration = 2;
ZonedDateTime current = initial.plusHours(appointmentDuration);
System.out.println(current);
Output:
2019-03-10T11:00-03:00[America/Sao_Paulo]
To convert to an Instant for your database:
Instant inst = current.toInstant();
System.out.println(inst);
Output:
2019-03-10T14:00:00Z
Instants are time zone neutral, just a point in time, but print in UTC. Some JDBC drivers accept them for UTC times. If yours doesn’t happen to, you will need to give it an OffsetDateTime instead. Convert like this:
OffsetDateTime odt = current.toOffsetDateTime().withOffsetSameInstant(ZoneOffset.UTC);
System.out.println(odt);
Output:
2019-03-10T14:00Z
Note that I give UTC explicitly rather than relying on the JVM default. So this is explicitly in UTC. You notice that the date and time agree with what was printed from the Instant.
Calendar c = Calendar.getInstance();
System.out.println(c.getTime());
c.set(2007, 0, 1);
System.out.println(c.getTime());
Output:
Tue Sep 12 12:36:24 IST 2017
Mon Jan 01 12:36:24 IST 2007
But, When I use the same code in a different environment, Output changes to below:
Output:
Tue Sep 12 12:36:24 IST 2017
Mon Jan 01 12:36:24 GMT 2007
FYI, I tried to print the timezone of the calendar instance, before and after setting the values and both are in "IST".
I want to know the root cause of this.
The second output in your question is the correct and expected behaviour on a JVM running Irish time (Europe/Dublin). On September 12, 2017 Ireland is on summer time (DST). While it is not clearly documented, Date.toString() (which you invoke implicitly when printing the Date you get from c.getTime()) prints the date and time in the JVM’s time zone, which in September is rendered as IST for Irish Summer Time.
When you set the date on the Calendar object also using Irish time, the hour of day is preserved; in your case you get Jan 01 2007 12:36:24 Irish standard time. Now imagine the confusion if both Irish Summer Time and Irish Standard Time were rendered as IST. You would not be able to distinguish. Instead, since Irish standard time coincides with GMT, this is what Date.toString() prints when the date is not in the summer time part of the year (which January isn’t).
My guess is that your first output is from a JVM running India time. It too is rendered as IST, and since India doesn’t use summer time, the same abbreviation is given summer and winter.
java.time
Before understanding the explanation for the behaviour you observed, I posted a comment about the outdated and the modern Java date and time classes. I still don’t think the comment is way off, though. This is the modern equivalent of your code:
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Europe/Dublin"));
System.out.println(zdt);
zdt = zdt.with(LocalDate.of(2007, Month.JANUARY, 1));
System.out.println(zdt);
It prints
2017-09-12T11:45:33.921+01:00[Europe/Dublin]
2007-01-01T11:45:33.921Z[Europe/Dublin]
If you want to use the JVM’s time zone setting, use ZoneId.systemDefault() instead of ZoneId.of("Europe/Dublin"). As the name states, contrary to Date, ZonedDateTime does include a time zone. It corresponds more to the old Calendar class. As you can see, its toString method prints the offset from UTC (Z meaning zero offset) and the time zone name in the unambiguous region/city format. I believe that this leaves a lot less room for confusion. If you want to print the date in a specific format, use a DateTimeFormatter.
Appendix: sample output from your code
For the sake of completeness, here are the outputs from your code when running different time zones that may be rendered as IST:
Europe/Dublin (agrees with your second output)
Tue Sep 12 11:19:28 IST 2017
Mon Jan 01 11:19:28 GMT 2007
Asia/Tel_Aviv
Tue Sep 12 13:19:28 IDT 2017
Mon Jan 01 13:19:28 IST 2007
Asia/Kolkata (agrees with your first output)
Tue Sep 12 15:49:28 IST 2017
Mon Jan 01 15:49:28 IST 2007
You need to set time zone and you will get desired result.
TimeZone.setDefault(TimeZone.getTimeZone("IST"));
Here is a working code.
import java.util.Calendar;
import java.util.TimeZone;
public class Cal {
public static void main(String[] args) {
// TODO Auto-generated method stub
TimeZone.setDefault(TimeZone.getTimeZone("IST")); // Add this before print
Calendar c = Calendar.getInstance();
System.out.println(c.getTime());
c.set(2007, 0, 1);
System.out.println(c.getTime());
}
}
As per Doc "Typically, you get a TimeZone using getDefault which creates a TimeZone based on the time zone where the program is running. For example, for a program running in Japan, getDefault creates a TimeZone object based on Japanese Standard Time."
SO when you running in different timezone it is using as default timezone. Hope you clear now. I attach doc. please read.
To talk about this interesting behaviour:
The source code from the Calendar class:
public final void set(int year, int month, int date)
{
set(YEAR, year);
set(MONTH, month);
set(DATE, date);
}
Which leads to the set method:
public void set(int field, int value)
{
// If the fields are partially normalized, calculate all the
// fields before changing any fields.
if (areFieldsSet && !areAllFieldsSet) {
computeFields();
}
internalSet(field, value);
isTimeSet = false;
areFieldsSet = false;
isSet[field] = true;
stamp[field] = nextStamp++;
if (nextStamp == Integer.MAX_VALUE) {
adjustStamp();
}
}
The interesting part here is the computeFields() method which has two implementation (one for Gregorian and one for Japenese calendar). These methods are quite complex but as far as I can see this is the only place where your Calendar instance may change time zone in your usecase.
Why cannot I clear the time from a timestamp this way:
one day == 24 * 3600 * 1000 == 86400000 milliseconds.
long ms = new Date().getTime(); //Mon Sep 03 10:06:59 CEST 2012
Date date = new Date(ms - (ms % 86400000));
how come this is Mon Sep 03 02:00:00 CEST 2012 instead of Mon Sep 03 00:00:00 CEST 2012?
Why cannot I clear time from timestamm this way
You're correctly clearing the time part in UTC. The millisecond values in Date are always relative to January 1st 1970 midnight in UTC. However, you're not displaying it in UTC, because of the way Date.toString() works (it always uses the system local time zone). Note that a Date itself has no concept of a time zone. It's just a number of milliseconds since January 1st 1970 midnight UTC.
The concept of "clearing a time from a timestamp" doesn't really make sense without specifying which time zone you're talking about, as the same timestamp will have different times of day (and even dates) in different time zones.
To be honest, I would suggest using Joda Time for any significant date/time work. Then you can create a LocalDate which is obviously meant to represent "just a date" - and the translation from a Date (or Instant) to a LocalDate will make it easy for you to specify whichever time zone you want to use.
I actually want to compare it to another date not taking into account time of day
To compare dates I suggest using JodaTime which supports this functionality with LocalDate
LocalDate date1 = new LocalDate(); // just the date without a time or time zone
LocalDate date2 = ....
if (date1.compareTo(date2) <=> 0)
Note: this will construct timezone-less LocalDates which is appropriate for the default timezone. As long as you are only talking about the timezone where the default timezone for the machine has been set, this is fine. e.g. say you have a timezone of CEST then this is fine for most of Europe.
Using the built in time functions you can do something like
public static int compareDatesInTimeZone(Date d1, Date d2, TimeZone tz) {
long t1 = d1.getTime();
t1 += tz.getOffset(t1);
long t2 = d2.getTime();
t2 += tz.getOffset(t2);
return Double.compare(t1 / 86400000, t2 / 86400000);
}
Try this...
Calendar c = Calendar.getInstance();
DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
String strDate = df.format(c.getTime()));
Now this way you can have the another date, and then compare it....as they are now in String format.