We have intervals (elapsed time between two oracle timestamps) stored in our database as seconds and we format them at front end with Java.
What we would to achieve on the reports is a format of the form "HH:MM" or "HH:MM:SS", with the time separator ":" localized as it happens with dates and time information, i.e '.' for Italian and ':' for English.
Unfortunately the date-related formatting classes, like SimpleDateFormat, do not work** because we can expect durations above the 24 hours.
We don't want to employ 3rdy party library as well.
Do you know how we can solve this problem?
TIA
If you want hours of more than 24 you can print this separately.
int hour = time / 3600000;
String duration = time + new SimpleDateFormat(":mm:ss").format(new Date(time));
To support other locales you could do this more complicated example.
int hour = time / 3600000;
String duration = hour
+ DateFormat.getDateInstance(DateFormat.MEDIUM, locale)
.format(new Date(time % 3600000).substring(1);
What this will do is use the locale specific format for the last digit of the hour + mins + secs and prepend the additional digits of the hours. Note: this will not work for negative times.
You can construct a date object, say "2000-01-01", then add your seconds to it. Then you can use this to extract the localized version of the time:
DateFormat format = DateFormat.getTimeInstance(DateFormat.MEDIUM, locale);
String formatted_time_part = format.format(some_date_object);
Finally you still need to add elapsed days! I'm affraid that localization of long intervals (days, months, years, centuries) has no corresponding API in Java but I might be wrong. So you will have to figure out that for yourself.
Related
Part of my current project is to convert mm:ss to seconds...but the user has the option to enter x:xx or xx:xx.
For example, if someone wanted to enter one minute and thirty seconds into the program, they have the option to write it as either "01:30" or "1:30". And the output of both would be 90 seconds.
This is my current code.
System.out.print("Time (mm:ss): ")
String time = scan.nextLine();
int min = Integer.parseInt(time.substring(0, time.indexOf(':'))) * 60;
int sec = Integer.parseInt(time.substring(3, time.length()));
int duration = (min + sec);
System.out.println("Seconds: " + duration)
It works whenever I enter xx:xx, but fails when I enter x:xx.
I am not sure how to only read the characters after ":" . If I start the substring at ":" (I have it at 3 now), it can't convert to int because it reads the ":".
I have looked all over Google and my textbook, but have not found anything. I assume I am just using the wrong technique. The code needs to stay within the parameters of basic beginner String methods. Thank you!
This answer probably does not stay within the parameters of basic beginner String methods as requested. I think it will be useful for other readers of your question who don’t have the same limitation.
java.time.Duration
The Duration class is the class to use for an amount of time like 1 minute 30 seconds. Unfortunately, the Duration class can only parse strings in ISO 8601 format (link below), but the string conversion isn’t hard with a regular expression. And fortunately in ISO 8601 format leading zeroes don’t matter. The Duration class is part of java.time, the modern Java date and time API.
ISO 8601 format for a duration is like PT01M30S. Think of it as a period of time of 01 minute 30 seconds. If the format feels unusual at first, it is straightforward. So let’s convert to it. The following method accepts your user’s format, converts and returns a Duration.
public static Duration parseDuration(String durStr) {
String isoString = durStr.replaceFirst("^(\\d{1,2}):(\\d{2})$", "PT$1M$2S");
return Duration.parse(isoString);
}
Duration has a toSeconds method for converting to seconds. So let’s try the whole thing out:
System.out.println(parseDuration("01:30").toSeconds());
System.out.println(parseDuration("1:30").toSeconds());
Output is the expected:
90
90
Consider whether you need to convert to seconds at all, though. Keeping the Duration objects as they are will probably make your code more self-explanatory.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Wikipedia article: ISO 8601
int sec = Integer.parseInt(time.substring(time.indexOf(':') + 1), time.length()));
Thanks guys!
Here I have simplified your code.
String time = "1:30";
String[] timeUnit = time.split(":");
int totalSeconds = 60 * Integer.parseInt(timeUnit[0]) + Integer.parseInt(timeUnit[1]);
System.out.println("Seconds "+totalSeconds);
From the front end I am receiving a separate LocalDate (variable name is date), along with separate Integers for hours, minutes, seconds, and an "AM" or "PM" String, and I need to combine these into a java.time.Instant object to store in the database. I tried to construct a LocalTime as follows, adding 12 hours if this is a PM time and then constructing an Instant:
LocalTime time = LocalTime.of("pm".equals(amPm) ? hours + 12: hours, minutes, seconds);
Instant instant = date.atTime(time).toInstant(ZoneOffset.UTC);
But when I store and reload the page, though the date is always intact, the time is always being changed. If I set the date to 1/29/1900 and the time to 07:01:01 AM, the Instant I am creating and storing has the value: 1900-01-29T07:01:01Z when I debug, which appears correct, but when the page reloads, the time says 02:01:01 AM, and that is the time that is stored in the database.
Am I constructing the time or the instant incorrectly?
There’s hardly any doubt that your unexpected observations are due to one or more time zone issues.
So the first thing you need to do is make sure you know which time zones are involved.
Which time zone is your front end using for sending date and time to you?
Which time zone is your database using for storing the date and time and displaying them to you when you check them? (UTC would be recommended for storing the times.)
Once you know this you can check:
Is the conversion from 1/29/1900 07:01:01 AM from the front end in some time zone to an Instant of 1900-01-29T07:01:01Z correct? The Instant displays its time in UTC (denoted by the trailing Z).
Is the conversion from the Instant to 02:01:01 AM in the database time zone correct?
Is the time being fetched correctly from the database? I am assuming you are fetching it back into Java?
Is the time you’ve got in Java being converted correctly to 02:01:01 AM on the front end? Again I am assuming that on page reload your are displaying the time fetched from the database, but I don’t think you have told us, so I could be wrong.
To answer your question:
Am I constructing the time or the instant incorrectly?
It depends; it’s certainly possible.
Your construction of the time is assuming that pm is always in lower case and that 12 o’clock (midnight or noon) is given as 0. On one hand I find both assumptions more or less unlikely, on the other hand they cannot account for the discrepancy of 5 hours that you observed. 12 would conventionally be given as 12 (not 0) on a 12 hour clock. And your question gives PM in upper case.
Your construction of the Instant assumes that the front end sent the time in UTC. To me this sounds unlikely too, and it may be the reason or one of the reasons why you observed an incorrect time being displayed back after page reoload.
Code example
In the following snippet I am making the opposite assumptions: 12 is given as 12, AM/PM may be in any case, and the front end time zone is America/New_York. It’s probably way off, but there may be a detail that you can pick and use for your purpose.
DateTimeFormatter timeFormatter = new DateTimeFormatterBuilder()
.parseCaseInsensitive() // Accept all of am, AM, aM and Am
.appendPattern("h:m:sa")
.toFormatter(Locale.US);
ZoneId zone = ZoneId.of("America/New_York");
LocalDate date = LocalDate.of(1900, Month.JANUARY, 29);
int hours = 7;
int minutes = 1;
int seconds = 1;
String amPm = "AM";
String constructedTimeString
= "" + hours + ':' + minutes + ':' + seconds + amPm;
LocalTime time = LocalTime.parse(constructedTimeString, timeFormatter);
Instant instant = date.atTime(time).atZone(zone).toInstant();
System.out.println(instant);
Output is:
1900-01-29T12:01:01Z
Geeky section: avoiding formatting time into a string and parsing it back
I couldn’t help thinking about whether it would be possible to have java.time parse the AM/PM string without having to construct a string for the time of day and parse it. It is possible, but we need to use the low-level TemporalAccessor interface, which is otherwise usually unnecessary.
DateTimeFormatter amPmFormatter = new DateTimeFormatterBuilder()
.parseCaseInsensitive() // Accept all of am, AM, aM and Am
.appendPattern("a")
.toFormatter(Locale.US);
int hours = 7;
int minutes = 1;
int seconds = 1;
String amPm = "AM";
TemporalAccessor parsedAmPm = amPmFormatter.parse(amPm);
LocalTime time = LocalTime.of(0, minutes, seconds)
.with(ChronoField.AMPM_OF_DAY, parsedAmPm.get(ChronoField.AMPM_OF_DAY))
.with(ChronoField.CLOCK_HOUR_OF_AMPM, hours);
System.out.println(time);
07:01:01
Construction of the Instant proceeds as before.
I'm using this library I just discovered which is supposedly less heavier than Joda time for android and I said what the heck, let's use it. But now I'm struggling to find any good examples on the web about how to use it, besides these two methods I have:
// ZonedDateTime contains timezone information at the end
// For example, 2011-12-03T10:15:30+01:00[Europe/Paris]
public static ZonedDateTime getDate(String dateString) {
return ZonedDateTime.parse(dateString).withZoneSameInstant(ZoneId.of("UTC"));
}
public static String formatDate(String format, String dateString) {
return DateTimeFormatter.ofPattern(format).format(getDate(dateString));
}
So how can I get the difference between two dates with this library?
There are several options depending on what you require from the difference you obtain.
It’s easiest to find the difference measured in some time unit. Use ChronoUnit.between. For example:
ZonedDateTime zdt1 = getDate("2011-12-03T10:15:30+01:00[Europe/Paris]");
ZonedDateTime zdt2 = getDate("2017-11-23T23:43:45-05:00[America/New_York]");
long diffYears = ChronoUnit.YEARS.between(zdt1, zdt2);
System.out.println("Difference is " + diffYears + " years");
long diffMilliseconds = ChronoUnit.MILLIS.between(zdt1, zdt2);
System.out.println("Difference is " + diffMilliseconds + " ms");
This prints:
Difference is 5 years
Difference is 188594895000 ms
I am using your getDate method, so the format required is that of ZonedDateTime (modified from ISO 8601), for example 2011-12-03T10:15:30+01:00[Europe/Paris]. Seconds and fraction of second are optional, as is time zone ID in square brackets.
BTW you don’t need to convert to UTC before finding the difference. You will get the same result even if you leave out that conversion.
You may also get the difference in years, months and days. The Period class can give you this, but it cannot handle time of day, so convert to LocalDate first:
Period diff = Period.between(zdt1.toLocalDate(), zdt2.toLocalDate());
System.out.println("Difference is " + diff);
Difference is P5Y11M21D
The output means a period of 5 years 11 months 21 days. The syntax may feel a little strange at first, but is straightforward. It is defined by the ISO 8601 standard. In this case the time zone matters since it is never the same date in all time zones.
To get the difference in hours, minutes and seconds use the Duration class (I am introducing a new time since using Duration for nearly 6 years would be too atypical (though possible)).
ZonedDateTime zdt3 = getDate("2017-11-24T18:45:00+01:00[Europe/Copenhagen]");
Duration diff = Duration.between(zdt2, zdt3);
System.out.println("Difference is " + diff);
Difference is PT13H1M15S
A period of 13 hours 1 minute 15 seconds. The T that you already know from 2011-12-03T10:15:30+01:00[Europe/Paris] here too separates the date part from the time part so you know that in this case 1M means 1 minute, not 1 month.
Suppose there are two dates A(start time) & B(end time). A & B could be the time on the same day or even different day. My task is to show difference in seconds. Date format which i am using is
Date Format :: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
For e.g.
start date :: "2011-11-16T14:09:23.000+00:00"
end date :: "2011-11-17T05:09:23.000+00:00"
Help is appreciated.
Use the Seconds class:
DateTime now = DateTime.now();
DateTime dateTime = now.plusMinutes(10);
Seconds seconds = Seconds.secondsBetween(now, dateTime);
System.out.println(seconds.getSeconds());
This piece of code prints out 600. I think this is what you need.
As further advice, explore the documentation of joda-time. It's pretty good, and most things are very easy to discover.
In case you need some help with the parsing of dates (It's in the docs, really), check out the related questions, like this:
Parsing date with Joda with time zone
The answer of #pcalcao will be best in most cases. Be aware that seconds will be rounded to an integer.
If you are interested in sub-seconds accuracy just substract the milliseconds:
double seconds = (now.getMillis() - dateTime.getMillis()) / 1000d;
I spent all the morning trying to find out a way to achieve what I initially thought would be an relatively easy task: convert a duration of time expressed in a numeric way into a readable way. For example, for an input of 3.5, the output should be "3 years and 6 months".
According to what I was reading, it seems that Joda Time library is strongly recommended. Using that library and following this post I was trying things like:
Period p = new Period(110451600000L); // 3 years and a half
PeriodFormatter formatter = new PeriodFormatterBuilder()
.appendYears()
.appendSuffix(" year", " years")
.appendSeparator(" and ")
.appendMonths()
.appendSuffix(" month", " months")
.toFormatter();
System.out.println(formatter.print(p));
But the output is nothing. No idea why it's not working.
I also tried with the Apache DurationFormatUtils, but doesn't work.
Does anybody have an idea?
Thanks in advance.
After some research, tests, and the help of benjamin, I have a solution:
DateTime dt = new DateTime(); // Now
DateTime plusDuration = dt.plus(new Duration(110376000000L)); // Now plus three years and a half
// Define and calculate the interval of time
Interval interval = new Interval(dt.getMillis(), plusDuration.getMillis());
// Parse the interval to period using the proper PeriodType
Period period = interval.toPeriod(PeriodType.yearMonthDayTime());
// Define the period formatter for pretty printing the period
PeriodFormatter pf = new PeriodFormatterBuilder()
.appendYears().appendSuffix("y ", "y ")
.appendMonths().appendSuffix("m", "m ").appendDays()
.appendSuffix("d ", "d ").appendHours()
.appendSuffix("h ", "h ").appendMinutes()
.appendSuffix("m ", "m ").appendSeconds()
.appendSuffix("s ", "s ").toFormatter();
// Print the period using the previously created period formatter
System.out.println(pf.print(period).trim());
I found really useful the official documentation of Joda-Time and specially this post: Correctly defining a duration using JodaTime
Nevertheless, though it is working I'm not 100% happy because the output of the posted code above is "3y 6m 11h" and I don't understand the reason of those eleven hours :S Anyway, I only need precision of years and months so I believe is not a big problem. If anybody knows the reason and/or whether it could be a problem in certain scenarios, please make me know with a comment.
The period p in your code does not contain years or moths, thats why the formatter does not output anything at all. Using the formatter PeriodFormat.getDefault(), you would see that it contains hours, namely exactly 30681 = 110451600000 / 1000 / 60 / 60.
And this is why: Milliseconds can be converted to seconds, minutes and hours in a defined way. But calculating the number of days, moths or years is ambiguous, since the number of hours of a day can be different (time zone shifting), as can the number of days in a month and number of days in a year. See the documenation: http://joda-time.sourceforge.net/apidocs/org/joda/time/Period.html#Period%28long%29
As found there:
For more control over the conversion process, you have two options:
convert the duration to an Interval, and from there obtain the period
specify a period type that contains precise definitions of the day and larger fields, such as UTC