Formatting a Duration in Java 8 / jsr310 - java

I am transitioning a project from Joda-Time to java8's native time libraries, and I have run into a snag.
I have been unable to find a formatter for Duration. I would like to have a custom String format of, for instance, HHH+MM, where a Duration of 75 hours and 15 minutes would format as "75+15".
This was easy to do with Joda-Time by converting to period, and using a PeriodFormatter, but I have been unable to find this type of class in Java8. Am I missing something?

Java 9 and later: Duration::to…Part methods
In Java 9 the Duration class gained new to…Part methods for returning the various parts of days, hours, minutes, seconds, milliseconds/nanoseconds. See this pre-release OpenJDK source code.
Given a duration of 49H30M20.123S…
toNanosPart() = 123000000
toMillisPart() = 123
toSecondsPart() = 20
toMinutesPart() = 30
toHoursPart() = 1
toDaysPart() = 2
Remember that “days” here means chunks of 24-hours, ignoring dates on a calendar. If you care about dates, use Period class instead.
I do not know if any additional formatter features are added. But at least you will be able to more conveniently generate your own strings from numbers obtained via these new getter methods.
Java 8
Oddly enough, no convenient getter methods for these values were included in the first edition release of java.time in Java 8. One of very few oversights in the otherwise excellent design of the java.time framework.
See the related Question: Why can't I get a duration in minutes or hours in java.time?.

There is no period/duration-formatter in jsr-310, different from JodaTime. Not every feature of JodaTime was ported to JSR-310 (for example also not PeriodType). And in reverse JSR-310 has some features which are not available in JodaTime (for example localized weekday numbers or the strategy pattern approach with adjusters).
It might happen that Java 9 will introduce some kind of built-in period formatting (read something about this from S. Colebourne).
Conclusion: JSR-310 and JodaTime are not fully compatible to each other, so a lot of work can be required. I would not be so keen on migration as soon as possible. Do you need special features of JSR-310 which are not offered by JodaTime?
Additional note: You should also be aware of the fact that joda period (which includes all units from years to seconds) is not fully compatible with jsr310-period (only years, months, days) or jsr310-duration (only hours, minutes, seconds and fraction seconds).

There is no built-in method but you can access the number of hours/minutes without having to calculate them manually. Your specific format could look like:
Duration d = Duration.of(75, HOURS).plusMinutes(15);
long hours = d.toHours(); //75
long minutes = d.minusHours(hours).toMinutes(); //15
String HH_PLUS_MM = hours + "+" + minutes; //75+15
System.out.println(HH_PLUS_MM);
If the duration is guaranteed to be less than 24 hours, you can also use this trick:
String hhPlusMm = LocalTime.MIDNIGHT.plus(d).format(DateTimeFormatter.ofPattern("HH+mm"));

you can use the DurationFormatUtils from commons-lang3-time (for minutes you have to use "mm" as the format is the same as in SimpleDateFormat):
DurationFormatUtils.formatDuration(interval.toMillis(), "HHH+mm")
Sadly I found no way to exclude empty parts, like in my case days or hours could be 0, so I still had to roll my own.
Update: I have opened an issue for this on Apache commons.lang.time.DurationFormatUtils JIRA.

I know this is an old question but I recently ran into the same thing. There really should be a better solution than this, but this worked for me:
public static String millisToElapsedTime(long millis){
DateFormat fmt = new SimpleDateFormat(":mm:ss.SSS");
fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
return (millis/3600000/*hours*/)+fmt.format(new Date(millis));
}
Then, you could add this:
public static String durationToElapsedTime(Duration d){
return millisToElapsedTime(d.toMillis());
}

Related

Calculate time difference in minutes given specific date and time [duplicate]

The Java class library has a class named DateTime. DateTime has this method:
int daysBetween(DateTime other)
which returns the number of days between this and the parameter.
It doesn't have a method
int secondsBetween(DateTime other)
which I happen to need.
Is there a class which is similar to DateTime but has such a method?
Not familiar with DateTime...
If you have two Dates you can call getTime on them to get millseconds, get the diff and divide by 1000. For example
Date d1 = ...;
Date d2 = ...;
long seconds = (d2.getTime()-d1.getTime())/1000;
If you have Calendar objects you can call
c.getTimeInMillis()
and do the same
I should like to provide the modern answer. The other answers were fine when this question was asked, but time moves on. Today I recommend you use java.time, the modern Java date and time API.
ZonedDateTime aDateTime = ZonedDateTime.of(2017, 12, 8, 19, 25, 48, 991000000, ZoneId.of("Europe/Sarajevo"));
ZonedDateTime otherDateTime = ZonedDateTime.of(2017, 12, 8, 20, 10, 38, 238000000, ZoneId.of("Europe/Sarajevo"));
long diff = ChronoUnit.SECONDS.between(aDateTime, otherDateTime);
System.out.println("Difference: " + diff + " seconds");
This prints:
Difference: 2689 seconds
ChronoUnit.SECONDS.between() works with two ZonedDateTime objects or two OffsetDateTimes, two LocalDateTimes, etc.
If you need anything else than just the seconds, you should consider using the Duration class:
Duration dur = Duration.between(aDateTime, otherDateTime);
System.out.println("Duration: " + dur);
System.out.println("Difference: " + dur.getSeconds() + " seconds");
This prints:
Duration: PT44M49.247S
Difference: 2689 seconds
The former of the two lines prints the duration in ISO 8601 format, the output means a duration of 44 minutes and 49.247 seconds.
Why java.time?
The Date class used in several of the other answers is now long outdated. Joda-Time also used in a couple (and possibly in the question) is now in maintenance mode, no major enhancements are planned, and the developers officially recommend migrating to java.time, also known as JSR-310.
Question: Can I use the modern API with my Java version?
If using at least Java 6, you can.
In Java 8 and later the new API comes built-in.
In Java 6 and 7 get the ThreeTen Backport, the backport of the new classes (ThreeTen for JSR 310).
On Android, use the Android edition of ThreeTen Backport. It’s called ThreeTenABP, and there’s a thorough explanation in this question: How to use ThreeTenABP in Android Project.
You should do
org.joda.time.Seconds.secondBetween(date1, date2)
That should do it:
Date a = ...;
Date b = ...;
Math.abs(a.getTime()-b.getTime())/1000;
Here the relevant documentation: Date.getTime(). Be aware that this will only work for dates after January 1, 1970, 00:00:00 GMT
You can use org.apache.commons.lang.time.DateUtils to make it cleaner:
(firstDate.getTime() - secondDate.getTime()) / DateUtils.MILLIS_PER_SECOND
There is no such class as DateTime in the standard Java SE API. Although there is one in joda-time, even that does not have a daysBetween method.
Using the standard Java API, the easiest way to get seconds between two java.util.Date objects would be to subtract their timestamps and divide by 1000:
int secondsBetween = (date1.getTime() - date2.getTime()) / 1000;
It is not recommended to use java.util.Date or System.currentTimeMillis() to measure elapsed times. These dates are not guaranteed to be monotonic and will changes occur when the system clock is modified (eg when corrected from server). In probability this will happen rarely, but why not code a better solution rather than worrying about possibly negative or very large changes?
Instead I would recommend using System.nanoTime().
long t1 = System.nanoTime();
long t2 = System.nanoTime();
long elapsedTimeInSeconds = (t2 - t1) / 1000000000;
EDIT
For more information about monoticity see the answer to a related question I asked, where possible nanoTime uses a monotonic clock. I have tested but only using Windows XP, Java 1.6 and modifying the clock whereby nanoTime was monotonic and currentTimeMillis wasn't.
Also from Java's Real time doc's:
Q: 50. Is the time returned via the
real-time clock of better resolution
than that returned by
System.nanoTime()?
The real-time clock and
System.nanoTime() are both based on
the same system call and thus the same
clock.
With Java RTS, all time-based APIs
(for example, Timers, Periodic
Threads, Deadline Monitoring, and so
forth) are based on the
high-resolution timer. And, together
with real-time priorities, they can
ensure that the appropriate code will
be executed at the right time for
real-time constraints. In contrast,
ordinary Java SE APIs offer just a few
methods capable of handling
high-resolution times, with no
guarantee of execution at a given
time. Using System.nanoTime() between
various points in the code to perform
elapsed time measurements should
always be accurate.
If you're using Joda (which may be coming as jsr 310 in JDK 7, separate open source api until then) then there is a Seconds class with a secondsBetween method.
Here's the javadoc link: http://joda-time.sourceforge.net/api-release/org/joda/time/Seconds.html#secondsBetween(org.joda.time.ReadableInstant,%20org.joda.time.ReadableInstant)
For java 8+ you can use
ChronoUnit.SECONDS.between(temporal1,temporal2)
Which class ? Do you mean the Joda DateTime class ? If so, you can simply call getMillis() on each, and perform the appropriate subtraction/scaling.
I would recommend Joda for date/time work, btw, due to it's useful and intuitive API, and its thread-safety for formatting/parsing options.
Just a pointer:
If you're calculating the difference between two java.util.Date the approach of subtracting both dates and dividing it by 1000 is reasonable, but take special care if you get your java.util.Date reference from a Calendar object.
If you do so, you need to take account of daylight savings of your TimeZone since one of the dates you're using might take place on a DST period.
That is explained on Prasoon's link, I recommend taking some time to read it.
Use this method:
private Long secondsBetween(Date first, Date second){
return (second.getTime() - first.getTime())/1000;
}
Use time unit class.
long timeDifferentInSeconds = TimeUnit.MILLISECONDS.toSeconds(currentDate.getTime()) - TimeUnit.MILLISECONDS.toSeconds(previousDate.getTime());

Converting duration to years in Java8 Date API?

I have a date in the far past.
I found out what the duration is between this date and now.
Now I would like to know - how much is this in years?
I came up withthis solution using Java8 API.
This is a monstrous solution, since I have to convert the duration to Days manually first, because there will be an UnsupportedTemporalTypeException otherwise - LocalDate.plus(SECONDS) is not supported for whatever reason.
Even if the compiler allows this call.
Is there a less verbous possibility to convert Duration to years?
LocalDate dateOne = LocalDate.of(1415, Month.JULY, 6);
Duration durationSinceGuss1 = Duration.between(LocalDateTime.of(dateOne, LocalTime.MIDNIGHT),LocalDateTime.now());
long yearsSinceGuss = ChronoUnit.YEARS.between(LocalDate.now(),
LocalDate.now().plus(
TimeUnit.SECONDS.toDays(
durationSinceGuss1.getSeconds()),
ChronoUnit.DAYS) );
/*
* ERROR -
* LocalDate.now().plus(durationSinceGuss1) causes an Exception.
* Seconds are not Supported for LocalDate.plus()!!!
* WHY OR WHY CAN'T JAVA DO WHAT COMPILER ALLOWS ME TO DO?
*/
//long yearsSinceGuss = ChronoUnit.YEARS.between(LocalDate.now(), LocalDate.now().plus(durationSinceGuss) );
/*
* ERROR -
* Still an exception!
* Even on explicitly converting duration to seconds.
* Everything like above. Seconds are just not allowed. Have to convert them manually first e.g. to Days?!
* WHY OR WHY CAN'T YOU CONVERT SECONDS TO DAYS OR SOMETHING AUTOMATICALLY, JAVA?
*/
//long yearsSinceGuss = ChronoUnit.YEARS.between(LocalDate.now(), LocalDate.now().plus(durationSinceGuss.getSeconds(), ChronoUnit.SECONDS) );
Have you tried using LocalDateTime or DateTime instead of LocalDate? By design, the latter does not support hours/minutes/seconds/etc, hence the UnsupportedTemporalTypeException when you try to add seconds to it.
For example, this works:
LocalDateTime dateOne = LocalDateTime.of(1415, Month.JULY, 6, 0, 0);
Duration durationSinceGuss1 = Duration.between(dateOne, LocalDateTime.now());
long yearsSinceGuss = ChronoUnit.YEARS.between(LocalDateTime.now(), LocalDateTime.now().plus(durationSinceGuss1) );
System.out.println(yearsSinceGuss); // prints 600
Although the accepted answer of #Matt Ball tries to be clever in usage of the Java-8-API, I would throw in following objection:
Your requirement is not exact because there is no way to exactly convert seconds to years.
Reasons are:
Most important: Months have different lengths in days (from 28 to 31).
Years have sometimes leap days (29th of February) which have impact on calculating year deltas, too.
Gregorian cut-over: You start with a year in 1415 which is far before first gregorian calendar reform which cancelled full ten days, in England even 11 days and in Russia more. And years in old Julian calendar have different leap year rules.
Historic dates are not defined down to second precision. Can you for example describe the instant/moment of the battle of Hastings? We don't even know the exact hour, just the day. Assuming midnight at start of day is already a rough and probably wrong assumption.
Timezone effects which have impact on the length of day (23h, 24h, 25h or even different other lengths).
Leap seconds (exotic)
And maybe the most important objection to your code:
I cannot imagine that the supplier of the date with year 1415 has got the intention to interprete such a date as gregorian date.
I understand the wish for conversion from seconds to years but it can only be an approximation whatever you choose as solution. So if you have years like 1415 I would just suggest following very simple approximation:
Duration d = ...;
int approximateYears = (int) (d.toDays() / 365.2425);
For me, it is sufficient in historic context as long as we really want to use a second-based duration for such an use-case. It seems you cannot change the input you get from external sources (otherwise it would be a good idea to contact the duration supplier and ask if the count of days can be supplied instead). Anyway, you have to ask yourself what kind of year definition you want to apply.
Side notes:
Your complaint "WHY OR WHY CAN'T JAVA DO WHAT COMPILER ALLOWS ME TO DO?" does not match the character of new java.time-API.
You expect the API to be type-safe, but java.time (JSR-310) is not designed as type-safe and heavily relies on runtime-exceptions. The compiler will not help you with this API. Instead you have to consult the documentation in case of doubt if any given time unit is applicable on any given temporal type. You can find such an answer in the documentation of any concrete implementation of Temporal.isSupported(TemporalUnit). Anyway, the wish for compile-safety is understandable (and I have myself done my best to implement my own time library Time4J as type-safe) but the design of JSR-310 is already set in stone.
There is also a subtile pitfall if you apply a java.time.Duration on either LocalDateTime or Instant because the results are not exactly comparable (seconds of first type are defined on local timeline while seconds of Instant are defined on global timeline). So even if there is no runtime exception like in the accepted answer of #Matt Ball, we have to carefully consider if the result of such a calculation is reasonable and trustworthy.
Use Period to get the number of years between two LocalDate objects:
LocalDate before = LocalDate.of(1415, Month.JULY, 6);
LocalDate now = LocalDate.now();
Period period = Period.between(before, now);
int yearsPassed = period.getYears();
System.out.println(yearsPassed);

How to handle full period in java.time?

The Period class in java.time handles only the date-oriented potion: years, months, days.
What about the time portion: hours, minutes, seconds?
How can we parse and generate string representations of full periods as defined in ISO 8601, PnYnMnDTnHnMnS? For example, a day and a half: P1DT12H. The academic year is nine months, P9M. Every year I get two weeks and 3 days of vacation, P17D. The customer occupied the hotel room for 2 days and seventeen and a half hours, P2DT17H30M.
The Period class in Joda-Time handles full period. Why not in java.time? Is there some other mechanism?
In Java SE 8, it is the responsibility of the application to create a class linking Period and Duration if that is needed.
Note that a Duration contains an amount of seconds, not separate amounts of seconds, minutes and hours. The amount of seconds can exceed 24 hours, thus a Duration can represent a "day". But it is a fixed 24 hours day. By contrast, the representation of a "day in Period is descriptive and takes into account DST. The state of a Period is formed from three separate fields - days, months and years.
Bear in mind that "The customer occupied the hotel room for 2 days and seventeen and a half hours, P2DT17H30M" has the possibility to be complicated by DST cutovers. Using Period and Duration separately things are clear - Period is affected by DST cutovers and Duration is not.
In design terms, the original java.time Period did include hours, minutes and seconds. However, this resulted in the need for many methods and complicated Javadoc to describe all the possibilities around normalization and DST. By separating the concepts, the interaction of each with the timeline is a lot clearer. Note that the two classes also relate to the SQL design ("year to month" and "day to second" concepts).
There are no current plans to add a new class for Java SE 9in this area, however it cannot be completely ruled out because XML/ISO-8601 allows a single combined representation.
org.threeten.extra.PeriodDuration
The ThreeTen-Extra project offers a class combining a Period and a Duration. Simply called PeriodDuration.
An amount of time in the ISO-8601 calendar system that combines a period and a duration.
This class models a quantity or amount of time in terms of a Period and Duration. A period is a date-based amount of time, consisting of years, months and days. A duration is a time-based amount of time, consisting of seconds and nanoseconds. See the Period and Duration classes for more details.
The days in a period take account of daylight saving changes (23 or 25 hour days). When performing calculations, the period is added first, then the duration.
Caveat: Be sure to read the Answer by JodaStephen to understand the issues involved in trying to combine Period and Duration. It rarely makes sense to do so in practice, though that is counter to our intuition.
FYI, ThreeTen-Extra, java.time in JSR 310, and Joda-Time are all led by the same man, Stephen Colebourne a.k.a. JodaStephen.
Short answer related to java.time (JSR-310):
No, that package does not offer a solution.
Alternatively, you can use the class Duration in the package javax.xml.datatype for parsing strings like PnYnMnDTnHnMnS. This is also available in older JDK-versions since Java-5. Example:
// parsing
String iso = "P2Y4M30DT17H5M57.123S";
javax.xml.datatype.Duration xmlDuration =
DatatypeFactory.newInstance().newDuration(iso);
int years = xmlDuration.getYears();
int months = xmlDuration.getMonths();
int days = xmlDuration.getDays();
int hours = xmlDuration.getHours();
int minutes = xmlDuration.getMinutes();
int fullSeconds = xmlDuration.getSeconds();
BigDecimal seconds = (BigDecimal) xmlDuration.getField(DatatypeConstants.SECONDS);
// generating ISO-string
String xml = xmlDuration.toString();
System.out.println(xml); // P2Y4M30DT17H5M57.123S
If you ask for limitations/issues, well, here you get a list:
Some (alternative) ISO-formats like P0001-04-20T4H cannot be parsed.
Some methods defined in javax.xml.datatype.Duration rely on an internal Calendar-instance (documented) so that those methods might not work if an instance of Duration holds very large values.
Working with fractional seconds might be awkward and sometimes limited in precision if operating on a Calendar-instance.
There is only one single normalization method using a Calendar-instance. At least this method takes into account DST-effects in a standard way.
Formatting (not even to mention localized printing) is not offered.
If you want to overcome those issues then you can consider an external library (and yes, I don't only think of Joda-Time whose precision is constrained to millisecs and whose internationalization is limited, too). Otherwise the package javax.xml.datatype has the advantage to save the effort to embed an external library into your classpath.
Update:
About the question in comment related to external libraries, I know Joda-Time and my library Time4J.
First one (Joda-Time) offers a special class called ISOPeriodFormat. This class is also able to parse alternative ISO-formats (although PyyyyWwwddThhmmss is not mentioned in original ISO-8601-paper while support for PYYYY-DDD is missing). Joda-Time defines a builder-driven approach for period formatters which can also be used for printing durations (periods). Furthermore, there is a limited support for localized printing (with version 2.9.3 of Joda-Time in 13 languages). Finally the class Period offers various normalization methods (see javadoc).
Second one (Time4J) offers the classes net.time4j.Duration and two formatting tools (Duration.Formatter for pattern-based printing/parsing and net.time4j.PrettyTime for localized printing in actually 78 languages). The class Duration offers for parsing ISO-strings the static method parsePeriod(String) and also various normalizing methods. Example for the interoperability with java.time (JSR-310) proving that this library can be considered and used as powerful extension of new java-8-date-time-api:
// input: using java.time-package
LocalDateTime start = LocalDateTime.of(2016, 3, 7, 10, 15, 8);
LocalDateTime stop = LocalDateTime.of(2016, 6, 1, 22, 15);
// define how you measure the duration (zone correction would also be possible)
Duration<?> duration =
TimestampInterval.between(start, stop).getDuration(
CalendarUnit.YEARS,
CalendarUnit.MONTHS,
CalendarUnit.DAYS,
ClockUnit.HOURS,
ClockUnit.MINUTES,
ClockUnit.SECONDS
);
// generate standard ISO-representation
String s = duration.toStringISO();
System.out.println(s); // P2M25DT11H59M52S
// parse ISO-String and prove equality with original
System.out.println(Duration.parsePeriod(s).equals(duration)); // true
// adding duration to <start> yields <stop>
System.out.println(start.plus(duration.toTemporalAmount())); // 2016-06-01T22:15
// format in human time
System.out.println(PrettyTime.of(Locale.US).print(duration));
// output: 2 months, 25 days, 11 hours, 59 minutes, and 52 seconds
For completeness I should also mention ocpsoft.PrettyTime but I am not sure if that library is able to process ISO-strings. It is rather designed for relative times.

Best Duration type in Java or .Net

I have seen way to many places where a method takes a long or an int to represent durations in either nanoseconds, milliseconds (most common), seconds and even days. This is a good place to look for errors, too.
The problem is also quite complex once you realize that you can mean for the duration to be a certain number of seconds, or an interval that fits the human perception of time better, so that a duration of 24 hours is always going to be the next day at the same "wall-clock" time. Or that a year is either 365 or 366 days depending on the date, so that a year from 28th of February is always going to be the 28th of February.
Why is there no distinct type to represent this? I have found none in either Java or .net
In .Net you can use the TimeSpan structure to represent a length of time.
For Java, take a look at Joda (an intuitive and consistent date/time library) and its Duration and Period classes. DateTime objects can handle addition and manipulation via these objects.
(answer changed to reflect the comments below re. the Period class)
It's not an easy problem. Maybe Joda-Time would be a useful library for you. It has a Duration class that can do what you are asking for.

Why is January month 0 in Java Calendar?

In java.util.Calendar, January is defined as month 0, not month 1. Is there any specific reason to that ?
I have seen many people getting confused about that...
It's just part of the horrendous mess which is the Java date/time API. Listing what's wrong with it would take a very long time (and I'm sure I don't know half of the problems). Admittedly working with dates and times is tricky, but aaargh anyway.
Do yourself a favour and use Joda Time instead, or possibly JSR-310.
EDIT: As for the reasons why - as noted in other answers, it could well be due to old C APIs, or just a general feeling of starting everything from 0... except that days start with 1, of course. I doubt whether anyone outside the original implementation team could really state reasons - but again, I'd urge readers not to worry so much about why bad decisions were taken, as to look at the whole gamut of nastiness in java.util.Calendar and find something better.
One point which is in favour of using 0-based indexes is that it makes things like "arrays of names" easier:
// I "know" there are 12 months
String[] monthNames = new String[12]; // and populate...
String name = monthNames[calendar.get(Calendar.MONTH)];
Of course, this fails as soon as you get a calendar with 13 months... but at least the size specified is the number of months you expect.
This isn't a good reason, but it's a reason...
EDIT: As a comment sort of requests some ideas about what I think is wrong with Date/Calendar:
Surprising bases (1900 as the year base in Date, admittedly for deprecated constructors; 0 as the month base in both)
Mutability - using immutable types makes it much simpler to work with what are really effectively values
An insufficient set of types: it's nice to have Date and Calendar as different things,
but the separation of "local" vs "zoned" values is missing, as is date/time vs date vs time
An API which leads to ugly code with magic constants, instead of clearly named methods
An API which is very hard to reason about - all the business about when things are recomputed etc
The use of parameterless constructors to default to "now", which leads to hard-to-test code
The Date.toString() implementation which always uses the system local time zone (that's confused many Stack Overflow users before now)
Because doing math with months is much easier.
1 month after December is January, but to figure this out normally you would have to take the month number and do math
12 + 1 = 13 // What month is 13?
I know! I can fix this quickly by using a modulus of 12.
(12 + 1) % 12 = 1
This works just fine for 11 months until November...
(11 + 1) % 12 = 0 // What month is 0?
You can make all of this work again by subtracting 1 before you add the month, then do your modulus and finally add 1 back again... aka work around an underlying problem.
((11 - 1 + 1) % 12) + 1 = 12 // Lots of magical numbers!
Now let's think about the problem with months 0 - 11.
(0 + 1) % 12 = 1 // February
(1 + 1) % 12 = 2 // March
(2 + 1) % 12 = 3 // April
(3 + 1) % 12 = 4 // May
(4 + 1) % 12 = 5 // June
(5 + 1) % 12 = 6 // July
(6 + 1) % 12 = 7 // August
(7 + 1) % 12 = 8 // September
(8 + 1) % 12 = 9 // October
(9 + 1) % 12 = 10 // November
(10 + 1) % 12 = 11 // December
(11 + 1) % 12 = 0 // January
All of the months work the same and a work around isn't necessary.
C based languages copy C to some degree. The tm structure (defined in time.h) has an integer field tm_mon with the (commented) range of 0-11.
C based languages start arrays at index 0. So this was convenient for outputting a string in an array of month names, with tm_mon as the index.
There has been a lot of answers to this, but I will give my view on the subject anyway.
The reason behind this odd behavior, as stated previously, comes from the POSIX C time.h where the months were stored in an int with the range 0-11.
To explain why, look at it like this; years and days are considered numbers in spoken language, but months have their own names. So because January is the first month it will be stored as offset 0, the first array element. monthname[JANUARY] would be "January". The first month in the year is the first month array element.
The day numbers on the other hand, since they do not have names, storing them in an int as 0-30 would be confusing, add a lot of day+1 instructions for outputting and, of course, be prone to alot of bugs.
That being said, the inconsistency is confusing, especially in javascript (which also has inherited this "feature"), a scripting language where this should be abstracted far away from the langague.
TL;DR: Because months have names and days of the month do not.
In Java 8, there is a new Date/Time API JSR 310 that is more sane. The spec lead is the same as the primary author of JodaTime and they share many similar concepts and patterns.
I'd say laziness. Arrays start at 0 (everyone knows that); the months of the year are an array, which leads me to believe that some engineer at Sun just didn't bother to put this one little nicety into the Java code.
Probably because C's "struct tm" does the same.
Because programmers are obsessed with 0-based indexes. OK, it's a bit more complicated than that: it makes more sense when you're working with lower-level logic to use 0-based indexing. But by and large, I'll still stick with my first sentence.
java.time.Month
Java provides you another way to use 1 based indexes for months. Use the java.time.Month enum. One object is predefined for each of the twelve months. They have numbers assigned to each 1-12 for January-December; call getValue for the number.
Make use of Month.JULY (Gives you 7)
instead of Calendar.JULY (Gives you 6).
(import java.time.*;)
Personally, I took the strangeness of the Java calendar API as an indication that I needed to divorce myself from the Gregorian-centric mindset and try to program more agnostically in that respect. Specifically, I learned once again to avoid hardcoded constants for things like months.
Which of the following is more likely to be correct?
if (date.getMonth() == 3) out.print("March");
if (date.getMonth() == Calendar.MARCH) out.print("March");
This illustrates one thing that irks me a little about Joda Time - it may encourage programmers to think in terms of hardcoded constants. (Only a little, though. It's not as if Joda is forcing programmers to program badly.)
For me, nobody explains it better than mindpro.com:
Gotchas
java.util.GregorianCalendar has far fewer bugs and gotchas than the
old java.util.Date class but it is still no picnic.
Had there been programmers when Daylight Saving Time was first
proposed, they would have vetoed it as insane and intractable. With
daylight saving, there is a fundamental ambiguity. In the fall when
you set your clocks back one hour at 2 AM there are two different
instants in time both called 1:30 AM local time. You can tell them
apart only if you record whether you intended daylight saving or
standard time with the reading.
Unfortunately, there is no way to tell GregorianCalendar which you
intended. You must resort to telling it the local time with the dummy
UTC TimeZone to avoid the ambiguity. Programmers usually close their
eyes to this problem and just hope nobody does anything during this
hour.
Millennium bug. The bugs are still not out of the Calendar classes.
Even in JDK (Java Development Kit) 1.3 there is a 2001 bug. Consider
the following code:
GregorianCalendar gc = new GregorianCalendar();
gc.setLenient( false );
/* Bug only manifests if lenient set false */
gc.set( 2001, 1, 1, 1, 0, 0 );
int year = gc.get ( Calendar.YEAR );
/* throws exception */
The bug disappears at 7AM on 2001/01/01 for MST.
GregorianCalendar is controlled by a giant of pile of untyped int
magic constants. This technique totally destroys any hope of
compile-time error checking. For example to get the month you use
GregorianCalendar. get(Calendar.MONTH));
GregorianCalendar has the raw
GregorianCalendar.get(Calendar.ZONE_OFFSET) and the daylight savings
GregorianCalendar. get( Calendar. DST_OFFSET), but no way to get the
actual time zone offset being used. You must get these two separately
and add them together.
GregorianCalendar.set( year, month, day, hour, minute) does not set
the seconds to 0.
DateFormat and GregorianCalendar do not mesh properly. You must
specify the Calendar twice, once indirectly as a Date.
If the user has not configured his time zone correctly it will default
quietly to either PST or GMT.
In GregorianCalendar, Months are numbered starting at January=0,
rather than 1 as everyone else on the planet does. Yet days start at 1
as do days of the week with Sunday=1, Monday=2,… Saturday=7. Yet
DateFormat. parse behaves in the traditional way with January=1.
The true reason why
You would think that when we deprecated most of Date and added the new
Calendar class, we would have fixed Date's biggest annoyance: the fact
that January is month 0. We certainly should have, but unfortunately
we didn't. We were afraid that programmers would be confused if Date
used zero-based months and Calendar used one-based months. And a few
programmers probably would have been. But in hindsight, the fact that
Calendar is still zero-based has caused an enormous amount of
confusion, and it was probably the biggest single mistake in the Java
international API's.
Quoted from International Calendars in Java by Laura Werner, link at the bottom.
The better alternative: java.time
This may just be repeating what others have said, throw the old and poorly designed Calendar class overboard and use java.time, the modern Java date and time API. There months are consistently sanely numbered from 1 for January through 12 for December.
If you are getting a Calendar from a legacy API not yet upgraded to java.time, the first thing to do is to convert to a modern ZonedDateTime. Depending on your needs you may do further conversions from there. In most of the world the Calendar object you get will virtually always be an instance of the GregorianCalendar subclass (since the Calendar class itself is abstract). To demonstreate:
Calendar oldfashionedCalendarObject = Calendar.getInstance();
ZonedDateTime zdt
= ((GregorianCalendar) oldfashionedCalendarObject).toZonedDateTime();
System.out.println(zdt);
System.out.format("Month is %d or %s%n", zdt.getMonthValue(), zdt.getMonth());
Output when I ran just now in my time zone:
2021-03-17T23:18:47.761+01:00[Europe/Copenhagen]
Month is 3 or MARCH
Links
International Calendars in Java by Laura Werner
Oracle tutorial: Date Time explaining how to use java.time.
tl;dr
Month.FEBRUARY.getValue() // February → 2.
2
Details
The Answer by Jon Skeet is correct.
Now we have a modern replacement for those troublesome old legacy date-time classes: the java.time classes.
java.time.Month
Among those classes is the Month enum. An enum carries one or more predefined objects, objects that are automatically instantiated when the class loads. On Month we have a dozen such objects, each given a name: JANUARY, FEBRUARY, MARCH, and so on. Each of those is a static final public class constant. You can use and pass these objects anywhere in your code. Example: someMethod( Month.AUGUST )
Fortunately, they have sane numbering, 1-12 where 1 is January and 12 is December.
Get a Month object for a particular month number (1-12).
Month month = Month.of( 2 ); // 2 → February.
Going the other direction, ask a Month object for its month number.
int monthNumber = Month.FEBRUARY.getValue(); // February → 2.
Many other handy methods on this class, such as knowing the number of days in each month. The class can even generate a localized name of the month.
You can get the localized name of the month, in various lengths or abbreviations.
String output =
Month.FEBRUARY.getDisplayName(
TextStyle.FULL ,
Locale.CANADA_FRENCH
);
février
Also, you should pass objects of this enum around your code base rather than mere integer numbers. Doing so provides type-safety, ensures a valid range of values, and makes your code more self-documenting. See Oracle Tutorial if unfamiliar with the surprisingly powerful enum facility in Java.
You also may find useful the Year and YearMonth classes.
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, & java.text.SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to java.time.
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 and 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 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….
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.
Set the month to Calendar.MARCH, or compare to see if it == Calendar.JUNE, for example.
The Date and Calendar classes date back to the very early days of Java, when folks were still figuring things out, and they are widely regarded as not very well designed.
If Calendar were created today with the same design, rather than ints for Calendar.JUNE, etc., they'd use enums.
It isn't exactly defined as zero per se, it's defined as Calendar.January. It is the problem of using ints as constants instead of enums. Calendar.January == 0.
Because language writing is harder than it looks, and handling time in particular is a lot harder than most people think. For a small part of the problem (in reality, not Java), see the YouTube video "The Problem with Time & Timezones - Computerphile" at https://www.youtube.com/watch?v=-5wpm-gesOY. Don't be surprised if your head falls off from laughing in confusion.
In addition to DannySmurf's answer of laziness, I'll add that it's to encourage you to use the constants, such as Calendar.JANUARY.
Because everything starts with 0. This is a basic fact of programming in Java. If one thing were to deviate from that, then that would lead to a whole slue of confusion. Let's not argue the formation of them and code with them.

Categories

Resources