How to handle negative ISO 8601 date string in java? - java

I am trying to convert ISO 8601 date string to epoch time. How do I handle negative dates? Is the below code correct? Should I use something else instead of simple date format library? Negative dates are for BC.
String formatString = "yyyy-MM-dd'T'hh:mm:ssX";
SimpleDateFormat formatter = new SimpleDateFormat(formatString);
Date date = formatter.parse("-2017-01-04T12:30:00+05:00");
System.out.println(date.getTime()/1000);
Answer: -125818806600L

TL;DR: No, your code is not correct. Yes, I recommend using the modern Java date and time API instead of SimpleDateFormat.
Your first issue is defining correctness for years before the common era (BCE, “before Christ”).
As I read Wikipedia, ISO 8601 does not define clearly how to interpret a date in that range. The year itself poses no great problem: “year 0000 being equal to 1 BCE”, so -1 is 2 BCE and -2017 is 2018 BCE. You may use the proleptic Gregorian calendar, produced by extending the Gregorian calendar backward to dates preceding its official introduction in 1582, but beware that this disagrees with the Julian calendar traditionally used, so when your date-time string says January 4th this is not the same day as January 4th in the history books. Also the use of negative years and the proplectic Gregorian calendar is not a requirement by ISO 8601, it is only by agreement between the parties. Reservation: I don’t know whether there is any definition of January 4, 2018 BCE in the history books; we’re way back before the introduction of the Julian calendar too (proposed by Julius Caesar in 46 BCE).
The documentation of SimpleDateFormat does not state how it handles dates before the introduction of the Gregorian calendar. It seems to depend on a Calendar object associated with the date/time formatter. Such a Calendar object would be a GregorianCalendar on most computers and JVMs, but not always. So I take it that the output from your code is not guaranteed to be the same on all computers. And a GregorianCalendar can and usually does handle dates from before pope Gregor in the Julian calendar, so I will expect that the result you got does agree with the history books, but not with ISO 8601, when it comes to establishing which day was January 4, 2018 BCE. So on these grounds I suspect that your result is not correct.
As a test I compared the output from your code with the output from a similar use of the Java date and time API. Running your code I too got -125818806600. So I tried:
System.out.println(OffsetDateTime.parse("-2017-01-04T12:30:00+05:00")
.toInstant()
.getEpochSecond());
These classes should be ISO 8601 compliant, so I would prefer this code over yours (it’s also a bit simpler). I got
-125817294600
It’s not the same, so another sign that your code does not give the correct result. The difference is 1512000 seconds, the same as 17 days 12 hours. Let me start by admitting I don’t understand. I would readily think that the difference between Julian and Gregorian calendar could account for a difference in the range of 17 or 18 days. But the 12 hours confuse me.
Edit: The 12 hours come from your use of lowercase hh in your format pattern string. Since you don’t have an AM/PM marker, you should use uppercase HH. Correcting this error, the output from your code is
-125818763400
Now the difference between your code and mine is 1468800 seconds or precisely 17 days.
hh is for hours within AM or PM in the range 1–12. Uppercase HH is for hour in day, 0–23. It’s a very common mistake with SimpleDateFormat (not with the modern classes, they catch it so you correct it). It goes unnoticed scaringly often because for most hours the result is the same; SimpleDateFormat is happy to use AM as default and parse for example 14:30 and understand it as 2:30 PM. But since the hours in your string happen to be 12, there is a difference: 12:30 AM means 0:30 in the day, where in ISO 12:30 means 12:30 PM. Hence the 12 hours error.

Related

How to print date 9999-99-99 in xmlgregoriancalendar?

Need to print the date exactly as "9999-99-99" using xmlgregoriancalendar type.
When i pass "9999-99-99" i get wrong output: 10007-07-08. How do i get output exactly as 9999-99-99
import java.util.GregorianCalendar;
import javax.xml.datatype.DatatypeFactory;
public class XMLGregorianCalendar {
public static void main(String[] args) {
/* Create Date Object */
//Date date = new Date();
javax.xml.datatype.XMLGregorianCalendar xmlDate = null;
//GregorianCalendar gc = new GregorianCalendar(2001,12,12);
GregorianCalendar gc = new GregorianCalendar(9999,99,99);
// gc.setTime(date);
try{
xmlDate = DatatypeFactory.newInstance().newXMLGregorianCalendar(gc);
}catch(Exception e){
e.printStackTrace();
}
System.out.println("XMLGregorianCalendar :- " + xmlDate);
}
}
Don’t
The XMLGregorianCalendar class was for dates and/or times for XML documents. Assuming that this was also your purpose, you must not put 9999-99-99 there. It’s not a valid date according to XML rules. Quoting XML Schema Part 2: Datatypes Second Edition, appendix D ISO 8601 Date and Time Formats:
M -- represents a digit used in the time element "month". The two digits in a MM format can have values from 1 to 12.
D -- represents a digit used in the time element "day". The two digits in a DD format can have values from 1 to 28 if the month value
equals 2, 1 to 29 if the month value equals 2 and the year is a leap
year, 1 to 30 if the month value equals 4, 6, 9 or 11, and 1 to 31 if
the month value equals 1, 3, 5, 7, 8, 10 or 12.
I have taken it out of context, but I think that we should understand that dates in XML documents need to be valid dates. 9999-99-99 is not a valid date since there is no month 99 and no month has 99 days in it.
If you wanted 9999-99-99 for something else than an XML document, I don’t think you should be using XMLGregorianCalendar at all. Without context I dare not suggest alternatives.
java.time I said “was … for XML documents”. Dates and times in XML documents are inspired from ISO 8601 formats and close enough that we usually can use the classes from java.time, the modern Java date and time API rather than XMLGregorianCalendar for them and still get the correct syntax from the toString methods of those classes. So also for valid dates consider using the modern LocalDate from java.time rather than the old XMLGregorianCalendar.
You cannot
XMLGregorianCalendar imposes the restriction of a valid date, so cannot print 9999-99-99.
What happened in your code was that GregorianCalendar tacitly and confusingly modified the date into a valid one. Try for example:
GregorianCalendar gc = new GregorianCalendar(9999,99,99);
System.out.println(gc.getTime());
On my computer I got:
Sun Jul 08 00:00:00 CEST 10007
When given invalid month and day of month, GregorianCalendar just keeps counting months and days into the following years. Since 99 months is a little more than 8 years, we end up more than 8 years after January 9999, and a further 3 months because of the 99 days. This was then the date that you passed to your XMLGregorianCalendar, which explains the output you got.
Links
XML Schema Part 2: Datatypes Second Edition, appendix D ISO 8601 Date and Time Formats
Wikipedia article: ISO 8601
Oracle tutorial: Date Time explaining how to use java.time.

How to print the current time and date in ISO date format in java?

I am supposed to send the current date and time in ISO format as given below:
'2018-02-09T13:30:00.000-05:00'
I have written the following code:
Date date = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm");
SimpleDateFormat formatter1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.000'Z'");
System.out.println(formatter.format(date));
System.out.println(formatter1.format(date));
It prints in the following way:
2018-04-30T12:02
2018-04-30T12:02:58.000Z
But it is not printing as the format mentioned above. How can I get the -5:00 as shown in the format and what does it indicate?
In java 8 you can use the new java.time api:
OffsetDateTime now = OffsetDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
System.out.println(formatter.format(now)); // e.g. 2018-04-30T08:43:41.4746758+02:00
The above uses the standard ISO data time formatter. You can also truncate to milliseconds with:
OffsetDateTime now = OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS);
Which yields something like (only 3 digits after the dot):
2018-04-30T08:54:54.238+02:00
Easy solution:
System.out.println(OffsetDateTime.now(ZoneId.of("America/Panama")).toString());
Just now I got this output:
2018-04-30T02:12:46.442185-05:00
To control that seconds are always printed with exactly three decimals:
DateTimeFormatter formatter
= DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSXXX");
OffsetDateTime now = OffsetDateTime.now(ZoneId.of("America/Panama"));
System.out.println(now.format(formatter));
2018-04-30T02:12:46.442-05:00
The first, the easy version will print enough groups of three decimals to render the full precision. It will also leave out the seconds completely if they happen to be 0.0. Both are probably OK because all of this is allowed within the ISO 8601 format that you asked for. So whoever receives the string should be happy anyway.
Please fill in your desired time zone where I used America/Panama. It’s best to give explicit time zone for predictable output.
I am using and recommending java.time, the modern Java date and time API. The SimpleDateFormat that you used is not only long outdated, it is also notoriously troublesome. java.time is so much nicer to work with.
What does -05:00 indicate?
-05:00 is an offset from UTC (or GMT, it is nearly the same thing). So your example string is probably from eastern time zone in North America or some other place in Central or Southern America (Cuba, Bolivia, to mention a few that use this offset for some of the year). More precisely -05:00 means that we’re using a clock that is 5 hours (and 0 minutes) behind UTC. So 2:12:46-05:00 denotes the same point in time as 7:12:46 UTC. If we only knew the time was 2:12:46 and didn’t know a time zone or offset, it would be very ambiguous. An offset is perfect for turning the time into an unambiguous point in time.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Wikipedia article: ISO 8601
Wikipedia article: UTC offset

Not able to work with Java Date Function properly

If I take current date from my application, it comes with variation like below:
scenario 1: when the date is less than 10th of the month, a month is less than 10 of the year --> example: 5/9/18
scenario 2: when the date is >= 10th of the month, a month is less >= 10 of the year --> example: 10/11/18
Note: all the examples are in MM/DD/YY format and timezone is the USA
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE,-2);
DateFormat dateFormat = new SimpleDateFormat("MM/dd/yy HH:mm a");
String PastDate = dateFormat.format(cal.getTime());
info("Date is displayed as : "+ PastDate );
The above piece of code throwing me an error when the scenario 1 is in place. But if I format the date-time as "M/d/yy H:mm a" it works for both the scenario. I need the date add also.
Will it be a good practice to use the 2nd format? or there is any other way to get it done. Expert guidance please..
java.time
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
.withLocale(Locale.US);
ZonedDateTime dayBeforeYesterday = ZonedDateTime.now(ZoneId.of("America/St_Thomas"))
.minusDays(2);
System.out.println(dayBeforeYesterday.format(formatter));
Running just now I got this output:
5/7/18, 8:44 AM
Please specify your desired time zone where I put America/St_Thomas. Think twice before you use ZoneId.systemDefault() for your JVM’s time zone setting since this setting may be changed at any time from other parts of your program or other programs running in the same JVM; but if you trust the setting reflects the user’s time zone, it’s the correct thing to use.
Rather than defining your own output format prefer using one of the built-in formats you get from DateTimeFormatter.ofLocalizedDateTime. Do specify locale (no matter if you use a built-in format or roll your own). Again, use Locale.getDefault() if you trust the JVM’s setting is correct.
Avoid the old date and time classes like Calendar, DateFormat and SimpleDateFormat. They are not only long outdated, they are also poorly designed and the last two in particular notoriously troublesome. Today we have so much better in java.time, the modern Java date and time API.
Link: Oracle tutorial: Date Time explaining how to use java.time.
The number of characters in the format MM indicates that two digits are required in the input. A single character M will match one or two digits. Use M/d/yy H:mm a to support your desired formats.

Difference between new Date() and Calendar date

What is the difference between the two dates below in practice?
Date date = new Date();
Date date = Calendar.getInstance().getTime();
What I understand is that new Date() is a UTC/GMT based date while calendar's getTime() is based on TimeZone & System time. Am I right? Do I miss something still?
Moreover, if my above understanding is correct, can I say that the end results of the following two functions are exactly the same ?
1.
public String getDate1(){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
//I set the time zone & pass the new Date()
sdf.setTimeZone(TimeZone.getDefault());
return sdf.format(new Date());
}
2.
public String getDate2(){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
//I didn't set the time zone because I think calendar instance will handle timezone change
return sdf.format(Calendar.getInstance().getTime());
}
I appreciate if you could point out where I understand wrongly & explain to me clearly. Because I feel this thing is confused to me. Thanks!
Practical info about Java Calendar and Date
If you want to operate with different dates in your Java program you will use Java Calendar class.
I will try to give you some overview of not widely known facts about Java Calendar and Date classes, working code examples, which you can try right away.
The basic information about Calendar class is provided by Java API. The Calendar class is about days, months and years. One could ask: is not Date class about the same? Not exactly...
What is difference between Java Date and Calendar classes?
The difference between Date and Calendar is that Date class operates with specific instant in time and Calendar operates with difference between two dates. The Calendar class gives you possibility for converting between a specific instant in time and a set of calendar fields such as HOUR, YEAR, MONTH, DAY_OF_MONTH. You can also manipulate with the calendar fields, for example getting the date of your grandmother birthday :).
I would like to point some things about Calendar and Date which you should know and which are not obvious...
Leap seconds.
Years, months, dates and hours are in "normal" range like:
A year y - 1900.
A month from 0 to 11
A date (day of month) from 1 to 31 in the usual manner. calendar leap seconds
An hour 0 to 23.
A minute from 0 to 59 in the usual manner.
But, attention!! A second is represented by an integer from 0 to 61. Looks strange - 61 second, but do not forget about leap second. About once every year or two there is an extra second, called a "leap second." The leap second is always added as the last second of the day, and always on December 31 or June 30. For example, the last minute of the year 1995 was 61 seconds long, thanks to an added leap second.
Lenient fields.
Another funny feature is lenient and non-lenient fields in calendar. What is that? Example:
32 January 2006. Actually if you set your calendar lenient it will be 1 February 2006 and no problem for your program :). If it is non-lenient ArrayIndexOutOfBoundsException exception will be thrown.
Another question is 00:00 end or beginning of day? Is 00:00 A.M. or P.M.? Are midnight and noon A.M. or P.M?
Answer: 23:59 is the last minute of the day and 00:00 is the first minute of the next day. Midnight belongs to "am", and noon belongs to "pm", so on the same day, 12:00 am (midnight) < 12:01 am, and 12:00 pm (noon) < 12:01 pm.
And probably last question: what is epoch? and why this Epoch since January 1, 1970 00:00:00.000 GMT.
Actually it is Unix time, or POSIX time, is a system for describing points in time: it is the number of seconds after 00:00:00 UTC, January 1, 1970.
Wait, one question more!
"If we use the time which is counted since Epoch, how can I know which years had leap seconds and which not?"
Answer: To make life easier leap seconds are not counted. Java Date class takes actual time from OS and most of modern computers can not use leap seconds, their's internal clocks are not so precised. That's why periodical time synchronization is required.
There is no difference between at all between those two dates. (The second one is of course a bit wasteful in allocating a Calendar object that you don't use.)
An instance of java.util.Date is an absolute point in time. It has no knowledge of time zones. Setting the Default timezone on the SimpleDateFormat similarly does nothing, it uses the default by.... default!
To try to explain in different terms, the java.util.Date for
10:49 pm Dec 19, 2013 UTC
And
5:49 pm Dec 19, 2013 US Eastern Time
Is exactly the same object. The exact same java.util.Date represents both of those human-readable representations of time. The human-readable considerations only come into play when you use the formatter to turn it back and forth. (Hence why you set the timezone on the formatter, not on the date, date has no knowledge of what a timezone means.)
In 2022, you MUST use java.time classes and you can refer here to know almost everything that needs to be known about time. But if you are using Java versions older than 8, or if you are curious, read on for some high-level overview.
1. Date date = new Date(); //Thu Mar 24 04:15:37 GMT 2022
2. Date date = Calendar.getInstance().getTime(); //Thu Mar 24 04:15:37 GMT 2022
Date(Does not have a notion of timezone, and is mutable, i.e not thread-safe)
Date is sufficient if you need only a current timestamp in your
application, and you do not need to operate on dates, e.g., one-week
later. You can further use SimpleDateFormat to control the date/time
display format.
Calendar(Abstract class, concrete implementation is GregorianCalendar)
Calendar provides internationalization support. Looking into the
source code reveals that: getInstance() returns a GregorianCalendar
instance for all locales, (except BuddhistCalendar for Thai ("th_TH")
and JapaneseImperialCalendar for Japanese ("ja_JP")).
Trivia
If you look at the Date java documentation, you will see many deprecated methods and the note:As of JDK version 1.1, replaced by Calendar.XXX. This means Calendar was a failed attempt to fix the issues that Date class had.
Bonus
You might want to watch this to get some more insights of Date vs Calendar

Date-String parsing problem (due to months from 0 to 11)

The code
String strDate = "2010-12-01";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd");
Date parsedDate = sdf.parse(strDate);
System.out.println(parsedDate);
will, dependend on your locale, produce the following output:
Fri Jan 01 00:12:00 CET 2010
The date is not parsed correctly, since i expect the 1st dec and not the 1st jan.
I know, months are numbered from 0 to 11, so the 12 becomes a 0 for january.
I have several solutions for this problem in mind, but all of them will produce at least 3-4 additional lines of code. So my question is:
What is the nicest way to solve this "problem"?
I can't imagine that it takes more than 2-3 lines to parse a simple date...
//edit: Shame on me for this question. Forgive me. thx folks
change yyyy-mm-dd to yyyy-MM-dd
M Month in year Month July; Jul; 07
m Minute in hour Number 30
See
SimpleDateFormat
Your date format is incorrect: Months are MM (not mm, which is for minutes). Try this:
"yyyy-MM-dd"
The reason you are getting January is that you haven't given a month to the parser (you gave year-minute-day). January, the first month, is the default month allocated to the date if not provided by the input. The 12 got parsed into the minute field (fairly obviously)
What is the nicest way to solve this "problem"?
Use different classes. You are using troublesome old legacy classes. Instead use the java.time classes.
LocalDate
The LocalDate class represents a date-only value without time-of-day and without time zone. It counts months sensibly, 1-12 is January through December.
Your input string is already in one of the ISO 8601 formats. These standard formats are used by default in the java.time classes. So no need with defining a formatting pattern.
LocalDate localDate = LocalDate.parse( "2010-12-01" );
Month
Also check out the handy Month enum.
Month month = Month.of( 1 ); // January = 1, December = 12.

Categories