In Joda-Time 2, what is the difference between the three kinds of time spans:
Period
Interval
Duration
Why do we need three classes?
Which one performs better?
Why is dividing a Period or Duration or Interval instance not implemented? E.g. p = p.divideBy(2);
3 classes are needed because they represent different concepts so it is a matter of picking the appropriate one for the job rather than of relative performance. From the documentation with comments added by me in italics:
An interval in Joda-Time represents an interval of time from one millisecond instant to another instant. Both instants are fully specified instants in the datetime continuum, complete with time zone. Specific times are defined e.g. this might be the interval between 20:00:00GMT yesterday and 09:00:00GMT this morning.
A duration in Joda-Time represents a duration of time measured in milliseconds. The duration is often obtained from an interval. i.e. we can subtract start from end of an interval to derive a duration
A period in Joda-Time represents a period of time defined in terms of fields, for example, 3 years 5 months 2 days and 7 hours. This differs from a duration in that it is inexact in terms of milliseconds. A period can only be resolved to an exact number of milliseconds by specifying the instant (including chronology and time zone) it is relative to. e.g. consider the period of 1 year, if we add this to January 1st we will always arrive at the next January 1st but the duration will depend on whether the intervening year is a leap year or not. Similarly if we add 1 month to the 1st of a month then we will arrive at the 1st of the next month but the duration (in milliseconds) will vary based on the month in question
For question 3, A specific method to divide a duration is not really needed because we can always get the number of milliseconds from the duration as a long (using getMillis()), divide it and construct a new duration (using new Duration(long duration)).
Dividing a period doesn't really have a real meaning based on the definition of a period above. e.g. what is half a month? (its length would depend on which month).
To add to mikej's answer:
A Joda-Time duration is a "physical" time interval; eg:
12000 milliseconds <-- this is a duration
A Joda-Time interval is actually a pair of instants (start instant - end instant). An instant is, again, a "physical" concept, a point in the timeline. Eg (just a possible notation):
(2010/3/3 19:00:00.000 UTC ; 2010/3/3 20:00:00.000 UTC) <-- this is an interval
An interval, then, can be converted to a duration, but not the reverse.
Consider these two intervals:
I1=(2010/3/3 19:00:00.000 UTC ; 2010/3/3 20:00:00.000 UTC)
I2=(2010/3/3 21:00:00.000 UTC ; 2010/3/3 22:00:00.000 UTC)
As intervals, I1 and I2 are different, because the end-points are different; but if I convert them to durations, I get the same thing: 3600000 milliseconds.
(Math analogy: the intervals [10,12] and [95,97] are different intervals, but they have the same length: "interval length" maps to duration).
Finally, a period is a lapse of "civil time", expressed as a number of months, days, hours, etc. It does not -by itself- represent a "physical" interval, hence it can't be directly converted to a duration (months have variable lengths...).
This answers question 3: you can only divide by two a physical time (a duration).
Related
I want to get a difference in hours between a current time in a specific timezone and UTC time. I tried this:
LocalTime time = LocalTime.now();
System.out.println(time); //21:05:42:764
LocalTime utcTime = LocalTime.now(ZoneId.of("UTC"));
System.out.println(utcTime); //18:05:42:769
System.out.println(Duration.between(utcTime, time).getSeconds()/3600); //2
System.out.println(Duration.between(time, utcTime).getSeconds()/3600); //-3
Why is the difference between the last two results and are there better ways to do it? I need to get the number 3.
Why is the difference between the last two results
The reason that you're getting different results for the two computed durations is a combination of the fact that there is some tiny amount of time elapsed between the two recordings and the fact that the duration start time is included in the range but the duration end time is not.
Consider these times instead: 6:00:00:001 vs 8:00:00:000. Here it is very obvious that we're only exactly one millisecond off of two hours, but when we think about seconds we're either going to get 7199 or -7200. When we then do integer math (i.e. divide by 3600), we're going to get 1 or -2.
If it weren't for the one extra millisecond on the first timestamp, the absolute value of the two would be identical.
Duration is the wrong class. There is zero duration between "now" in one time zone and "now" in another. For a fun but memorable way to think about this, see here.
You appear to be seeking to know the current offset from UTC for a given time zone. You can use the ZonedDateTime class for that:
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Kolkata"));
ZoneOffset offset = zdt.getOffset();
int offsetMinutes = offset.getTotalSeconds() / 60;
double offsetHours = ((double) offsetMinutes) / 60;
System.out.println(offsetHours); // 5.5
You could also just use ZonedDateTime.now() on the first line, if you want to use the computer's current time zone.
With regard to LocalTime - that is just the time portion (hours, minutes, seconds, and smaller). Since there is no date associated, you can't necessarily determine which time zone offset it belongs to. There is more than one date that "today" going on at any given moment. Time zone offsets range from UTC-12 to UTC+14, so there are indeed values where the same time of day is happening on two different dates somewhere on the planet.
As an example, 08:00:00 in Hawaii (Pacific/Honolulu) on 2019-01-01 is also 08:00:00 in Kiribati (Pacific/Kiritimati), but on 2019-01-02 - the following date! (Reference here.) Thus, if you had a LocalTime object with 08:00:00 and it was 08:00:00 in one of those two zones, you'd not be able to tell which one it was, or what the corresponding UTC offset should be.
Also, keep in mind that time zone offsets are not limited to whole hours. There are present-day time zones with half-hour and 45-minute offset. Historically, there have been others.
Lastly, keep in mind that an offset is not necessarily enough to identify a time zone. Many time zones share offsets at some points in time, but differ in others. See "Time Zone != Offset" in the timezone tag wiki.
Oh, and about your results getting 2 in one direction and -3 in the other - this is a rounding error due to your integer division. If you print out the seconds value, you'll notice they are one second apart (10799, vs -10800). Dig closer and you'll find that "now" included fractional seconds that were truncated with the getSeconds call. (You called .now() twice, so they were at slightly different times.)
How to get maximum time value of the current date in miliseconds in Java ?
E.g. 18th July 23.59 in epoch miliseconds where current time would be anything of 18th July
Half-Open
Seems like your Question involves determining the last moment of the day. Such a goal is ill-advised.
The last moment of the day is infinitely divisible as a fraction of a second, such as 2017-01-23T23:59:59.999Z. But different software uses different granularities when resolving such date-time values. The old legacy date-time classes is Java use milliseconds, or 3 decimal places. The new java.time classes use nanoseconds, or 9 decimal places. Other software such as the Postgres database use microseconds, for 6 decimal places. Other software uses other resolutions such as 5 decimal places. So you will be getting different values in different scenarios for the same date.
A better approach to defining spans of time is commonly used in date-time work, and avoids this ambiguity of last-moment-of-the-day: Half-Open. In this approach the beginning is inclusive while the ending is exclusive. So a single full day starts at the first moment of one date and runs up to, but does not include, the first moment of the following day. For UTC, that would be 2017-01-23T00:00:00Z to 2017-01-24T00:00:00Z.
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( z ) ;
ZonedDateTime todayStart = today.atStartOfDay( z ) ;
long secondsSinceEpoch = todayStart.toEpochSecond() ; // Whole seconds since epoch of 1970-01-01T00:00:00Z.
long millisecondsSinceEpoch = todayStart.toInstant().toEpochMilli() ; // Milliseconds since epoch of 1970-01-01T00:00:00Z.
See this code run live at IdeOne.com.
todayStart: 2017-07-19T00:00-04:00[America/Montreal]
millisecondsSinceEpoch: 1500436800000
For the following day (tomorrow), add one day to the LocalDate.
LocalDate tomorrow = today.plusDays( 1 ) ;
ZonedDateTime tomorrowStart = tomorrow.atStartOfDay( z ) ;
There’s room for a bit of interpretation in your question. I suggest:
ZoneId zone = ZoneId.systemDefault();
long endOfDay = LocalDate.now(zone)
.plusDays(1)
.atStartOfDay(zone)
.toInstant()
.minusMillis(1)
.toEpochMilli();
System.out.println(endOfDay);
On my computer it just printed
1500501599999
This corresponds to a ZonedDateTime of 2017-07-19T23:59:59.999+02:00[Europe/Berlin].
Please note that the end of the day is time zone dependent, so you need to decide a time zone for the operation. Please fill in your desired time zone instead of ZoneId.systemDefault().
Since Java doesn’t include an “end of the day” operation I opted for adding one day, taking the beginning of the day in the time zone in question and then subtracting 1 millisecond (because you asked for milliseconds since the epoch). If you want, you may instead subtract a nanosecond, a second or a minute, for example. If you want to subtract a whole minute, the simplest is to use .minusMinutes(1) before toInstant().
The code should be safe even on a day and in a time zone where a summer time transition (DST changeover) happens at midnight.
I'm not entirely sure how the specific date plays into this, but if you want to find the last millisecond of the day, I would use LocalTime.MAX.toNanoOfDay()/1000000. This takes the maximum nanosecond of the day, then divides by 1000000 to convert to milliseconds. If you wanted to combine this with a date, I would combine this value with a LocalDate or LocalDateTime value.
Can someone explain why the following tests fails?
#Test
public void testDuration() {
Duration duration = Duration.standardDays(1);
assertTrue(duration.getStandardMinutes() == 1440); //OK
assertTrue(duration.toPeriod().getMinutes() == 1440); //NOK. result = 0
assertTrue(new Period(duration.getMillis()).getMinutes() == 1400); //NOK. result = 0
}
A Period in JodaTime represents a "set of duration field values". Therefore, the methods getMinutes(), getHours(), ... are returning the value of those fields rather than computing the minutes, hours, ...
Furthermore, the conversion from a Duration sets the fields according to a PeriodType and a chronology.
From the API (http://joda-time.sourceforge.net/apidocs/org/joda/time/ReadableDuration.html#toPeriod%28%29):
Period toPeriod()
Converts this duration to a Period instance using the standard period type and the ISO chronology.
Only precise fields in the period type will be used. Thus, only the hour, minute, second and millisecond fields on the period will be used. The year, month, week and day fields will not be populated.
If the duration is small, less than one day, then this method will perform as you might expect and split the fields evenly. If the duration is larger than one day then all the remaining duration will be stored in the largest available field, hours in this case.
This means, as a Duration's day has exactly 24 hours, all information is stored in the hours field and minutes stays at 0.
See this question for a good explanation of the differences between Interval, Duration and Period.
See here
A period of 1 day is not equal to a period of 24 hours, nor 1 hour equal to 60 minutes. This is because periods represent an abstracted definition of a time period (eg. a day may not actually be 24 hours, it might be 23 or 25 at daylight savings boundary).
Given a period such as 3 days, or 5 weeks (a period with only one field type), I want to round a given DateTime to the nearest unit of that period (i.e, ignore the 5 in '5 days'). Examples:
Example 1:
Period: 3 days.
DateTime: Wednesday 4:26 AM UTC (2013-05-15T04:26:00Z)
Rounded DateTime: Wednesday Midnight UTC (2013-05-15T00:00:00Z)
Example 2:
Period: 5 weeks.
DateTime: Wednesday 4:26 AM UTC (2013-05-15T04:26:00Z)
Rounded DateTime: Monday Midnight UTC (2013-05-13T00:00:00Z)
My initial idea was to use Period's DurationFieldType getFieldTypes() method, and for every matching field in a DateTime (below the largest field), set them to zero. However, I don't know how to get the DateTimeFieldTypes from a DateTime and how to compare them to a DurationFieldType.
I would prefer not to do a huge if else approach.
Example bellow is a solution in case you can express period in days (can be modified to weeks, months etc.). Using DateTime Joda Java Library.
Unfortunately with rounding you require I see possible issue. You need to have a starting point in time since when you calculate the periods. In example bellow we calculate periods since 1970/01/01 00:00:00 UTC. Or are you actually asking to get period of 3 days from first day of a month (year) etc? It would make more sense.
Questions you need to ask your self: What will happen on leap days?
Java Method
DateTime roundDays(DateTime dt, int windowDays) {
Duration p = Duration.standardDays(windowDays);
long t = dt.getMillis() / p.getMillis() * p.getMillis();
// Keep TimeZone and round floor to a day
return new DateTime(t, dt.getZone()).dayOfMonth().roundFloorCopy();
}
Example use:
DateTime dt = new DateTime(1385578964580L, DateTimeZone.UTC);
System.out.println(roundDays(dt, 3));
System.out.println(roundDays(dt.plusDays(2), 3));
System.out.println(roundDays(dt.plusDays(4), 3));
System.out.println(roundDays(dt.plusDays(6), 3));
// Prints data rounded to every 3 days
// 2013-11-26T00:00:00.000Z
// 2013-11-29T00:00:00.000Z
// 2013-11-29T00:00:00.000Z
// 2013-12-02T00:00:00.000Z
Too long for comment:
It's not clear what that "rounding" means. To start with, you should deal with LocalDateTimes, not with DateTimes (they are very different things, see my answer here ).
It seems to me you want to set to zero all fields with resolution lower than that of your "period" unit, and then set the next field to a multiple of the given value... is that so? Then, I don't understand your second example (where are the 5 weeks?), and anyway, that would be badly specified: what to do with a period of "40 months" ?
I'm trying to get the total amount of Milliseconds (not the millis field) from the Period object instance. I've tried multiple conversions, as I couldn't find any method easily giving it.
Has anyone ever needed that and managed to retrieve it ?
(I need this for my patch, to figure out a negative period; negative millis = negative period.)
You can't get the millis directly from a Period, since fields like months and years are variable in terms of milliseconds.
In order to make this work, you need to supply a "baseline" instant from which Period can calculate that actual millisecond duration.
For example, the Period.toDurationFrom and Period.toDurationTo methods take such a baseline instant, and calculate a Duration object, which you can then obtain the millis.
The Javadoc for toDurationFrom says:
Gets the total millisecond duration of this period relative to a start instant.
This method adds the period to the specified instant in order to calculate the duration.
An instant must be supplied as the duration of a period varies. For example, a period of 1 month could vary between the equivalent of 28 and 31 days in milliseconds due to different length months. Similarly, a day can vary at Daylight Savings cutover, typically between 23 and 25 hours.
So you need to pick an appropriate baseline instant for your application.
If you want to get the millis from a specific time it can be done by using the plus() or minus() methods of the DateTime class.
e.g. getting the millis from now
DateTime start = new DateTime(); //NOW
DateTime end = start.plus(<your period>);
long millis = end.getMillis() - start.getMillis();
Using Joda time 2.3, it is:
toStandardDuration().getMillis()