Duration to TemporalUnits - java-time

How do I convert a duration to a set of temporal units? I have a duration, and I want to divide that into for instance whole years, whole days and fractional seconds.
I have found PeriodFormatterBuilder for JodaTime, but it seems to facilitate printing as well as division into units, i would just like the division.

I am not sure if you speak about the new java.time-package of Java-8 or about Joda-Time so I try to present solutions for both libraries.
Most important aspect however is that you cannot divide a Duration defined in seconds into years, months etc. in a self-consistent manner because month-based units vary in length of seconds and days. At least not possible without any trick.
Best you can do in this case is to use a reference timestamp in order to recalculate the duration you have. That means you add your duration to the reference timestamp and then evaluate the new duration between the old reference timestamp and the result in years, months, days etc. This is also called normalization and has nothing to do with printing/formatting.
Java-8:
Duration dur = Duration.ofSeconds(5000001); // example
LocalDateTime ref = LocalDateTime.now(); // reference timestamp
LocalDateTime end = ref.plus(dur);
System.out.println(ref);
System.out.println(end);
// normalize first the calendrical part
LocalDateTime ldt = ref;
long years = ChronoUnit.YEARS.between(ldt, end);
// find the months part
ldt = ldt.plus(years, ChronoUnit.YEARS);
long months = ChronoUnit.MONTHS.between(ldt, end);
// find the days part
ldt = ldt.plus(months, ChronoUnit.MONTHS);
long days = ChronoUnit.DAYS.between(ldt, end);
// find the hours part
ldt = ldt.plus(days, ChronoUnit.DAYS);
long hours = ChronoUnit.HOURS.between(ldt, end);
// find the minutes part
ldt = ldt.plus(hours, ChronoUnit.HOURS);
long minutes = ChronoUnit.MINUTES.between(ldt, end);
// find the seconds part
ldt = ldt.plus(minutes, ChronoUnit.MINUTES);
long seconds = ChronoUnit.SECONDS.between(ldt, end);
// print the new normalized duration in ISO-8601-format
System.out.println(
String.format("P%1$dY%2$dM%3$dDT%4$dH%5$dM%6$dS", years, months, days, hours, minutes, seconds));
// example output
// 2015-03-17T12:54:07.943
// 2015-05-14T09:47:28.943
// P0Y1M26DT20H53M21S
Compared with old JDK pre 8 this can be considered as much better because at least elementary methods for calculation of a duration in one given unit are offered. But a general duration type for handling all units spanning from years to seconds is completely missing. And the best duration formatter I could find is just java.util.Formatter.
Joda-Time
That is the second-best Java library when duration handling is needed, in most details better than Java-8 on this area. Joda-Time indeed offers a duration type spanning from years to seconds (and millis) called Period. See here the much simpler solution:
Duration dur = new Duration(5000001 * 1000L); // in milliseconds
LocalDateTime ref = new LocalDateTime(); // reference timestamp
LocalDateTime end = ref.plus(dur);
// construct normalized duration
PeriodType type = PeriodType.yearMonthDayTime().withMillisRemoved();
Period p = new Period(ref, end, type);
// print the new normalized duration
System.out.println(p); // P1M26DT20H53M21S
Small note: I have left out fractional seconds (in Joda-Time limited to milliseconds, in Java-8 up to nanoseconds) in given examples. It is easy to enhance the examples if you really have need for this precision.

Related

How to calculate the next minute and next 5 minute intevals given a ZonedDateTime

I have a instance of a ZonedDatetime.
ZonedDateTime.now(ZoneId.of("America/New_York"))
I basically need a function that will take an instance of a ZonedDateTime and return the next 1 minute and 5 minute values.
So if the current time is:
2021-10-24T19:46:10.649817
The next minute will be 19:47:00 and the next 5 minute will be 19:50:00
The next 5 minute interval is always like:
1:00
1:05
1:10
1:15
1:20
1:25
...
1:50
1:55
2:00
i.e. the next 5 minute interval is not based on exactly 5 minutes from now, but rather the next 5 minutes based on starting from the beginning of the hour. Same goes for the next 1 minute interval in the future.
def nextIntervals(zdt: ZonedDateTime): (ZonedDateTime, ZonedDateTime) = {
???
}
It is fairly simple to do so without hardcoding the values. Unfortunately I'm not familiar with scala so I'll give you some pseudo code, I believe you'll be able to easily translate it.
nextIntervals(zdt) {
timestamp = zdt.toUnixTimestamp();
return [
new ZonedDateTime(timestamp + (60 - timestamp % 60)),
new ZonedDateTime(timestamp + (300 - timestamp % 300))
]
}
The above code assumes that ZonedDateTime can be instantiated by giving it a unix timestamp, measured in seconds. And also that it can be converted to a unix timestamp.
The idea is pretty simple: the remainder of the modulus will be the time that has elapsed since the last required period (in your case 1 minute or 5 minutes). Take that away from the period itself and you have the time that's left until the next period. Add that to the current time and you have the exact datetime.
Edit:
Here's a working javascript example
function nextIntervals(date) {
let t = date.getTime();
return [
60e3,
300e3,
].map(i => new Date(t + i - t % i));
}
console.log(nextIntervals(new Date));
You can use the following functions to meet your requirements:
ZonedDateTime#plusMinutes
ZonedDateTime#minusMinutes
ZonedDateTime#truncatedTo
Demo:
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
public class Main {
public static void main(String[] args) {
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("America/New_York"));
ZonedDateTime nextMinute = now.plusMinutes(1).truncatedTo(ChronoUnit.MINUTES);
ZonedDateTime nextMultipleOfFiveMin = now.truncatedTo(ChronoUnit.MINUTES)
.minusMinutes(now.getMinute() % 5)
.plusMinutes(5);
System.out.println(now);
System.out.println(nextMinute);
System.out.println(nextMultipleOfFiveMin);
}
}
Output from a sample run:
2021-10-25T16:59:22.662943-04:00[America/New_York]
2021-10-25T17:00-04:00[America/New_York]
2021-10-25T17:00-04:00[America/New_York]
Output from another sample run after a while:
2021-10-25T17:05:09.596952-04:00[America/New_York]
2021-10-25T17:06-04:00[America/New_York]
2021-10-25T17:10-04:00[America/New_York]
ONLINE DEMO
Learn more about the modern Date-Time API from Trail: Date Time. Check this answer and this answer to learn how to use java.time API with JDBC.
Note: The java.util Date-Time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern Date-Time API*.
* If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring. Note that Android 8.0 Oreo already provides support for java.time.
We do need a little bit of hand-coded math to handle the 5-minute interval case. Excuse my Java syntax.
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("America/New_York"));
System.out.println("Now: " + now);
// Truncate to the previous 5 minutes
ZonedDateTime zdt = now.truncatedTo(ChronoUnit.MINUTES);
zdt = zdt.withMinute(zdt.getMinute() / 5 * 5);
for (int i = 0; i <= 12; i++) {
zdt = zdt.plusMinutes(5);
System.out.println(zdt);
}
Example output:
Now: 2021-10-25T15:23:31.357567-04:00[America/New_York]
2021-10-25T15:25-04:00[America/New_York]
2021-10-25T15:30-04:00[America/New_York]
2021-10-25T15:35-04:00[America/New_York]
2021-10-25T15:40-04:00[America/New_York]
2021-10-25T15:45-04:00[America/New_York]
2021-10-25T15:50-04:00[America/New_York]
2021-10-25T15:55-04:00[America/New_York]
2021-10-25T16:00-04:00[America/New_York]
2021-10-25T16:05-04:00[America/New_York]
2021-10-25T16:10-04:00[America/New_York]
2021-10-25T16:15-04:00[America/New_York]
2021-10-25T16:20-04:00[America/New_York]
2021-10-25T16:25-04:00[America/New_York]
The trick to truncate to a whole multiple of 5 minutes is to divide by 5, obtain a whole number and discard any remainder, and multiply by 5 again.
The 1-minute interval is similar, only a bit simpler: we don’t need to do any math ourselves, java.time takes care of it all.

Result of adding second to date is one minute off; workaround

I'm adding a second to an instance of Foundation's date, but the result is off by an entire minute.
var calendar = Calendar(identifier: .iso8601)
calendar.locale = Locale(identifier: "en")
calendar.timeZone = TimeZone(identifier: "GMT")!
let date1 = Date(timeIntervalSinceReferenceDate: -62544967141.9)
let date2 = calendar.date(byAdding: DateComponents(second: 1),
to: date1,
wrappingComponents: true)!
ISO8601DateFormatter().string(from: date1) // => 0019-01-11T22:00:58Z
ISO8601DateFormatter().string(from: date2) // => 0019-01-11T21:59:59Z
Interestingly, one of the following makes the error go away:
round time interval since reference date
don't add time zone to calendar
set wrappingComponents to false (even though it shouldn't wrap in this case)
I don't really need sub-second precision in my code, so I created this extension that allows me to discard it.
extension Date {
func roundedToSeconds() -> Date {
return Date(timeIntervalSinceReferenceDate: round(timeIntervalSinceReferenceDate))
}
}
I want to know this:
Why does this error happen?
Am I doing something wrong?
Is there any issue with my workaround?
Why does this error happen?
I would say this is a bug in Core Foundation (CF).
Calendar.date(byAdding:to:wrappingComponents:) calls down to the internal Core Foundation function _CFCalendarAddComponentsV, which in turn uses the ICU Calendar C API. ICU represents a time as an floating-point number of milliseconds since the Unix epoch, while CF uses a floating-point number of seconds since the NeXT reference date. So CF has to convert its representation to ICU's representation before calling into ICU, and convert back to return the result to you.
Here's how it converts from a CF timestamp to an ICU timestamp:
double startingInt;
double startingFrac = modf(*atp, &startingInt);
UDate udate = (startingInt + kCFAbsoluteTimeIntervalSince1970) * 1000.0;
The modf function splits a floating-point number into its integer and fractional parts. Let's plug in your example date:
var startingInt: Double = 0
var startingFrac: Double = modf(date1.timeIntervalSinceReferenceDate, &startingInt)
print(startingInt, startingFrac)
// Output:
-62544967141.0 -0.9000015258789062
Next, CF calls __CFCalendarAdd to add one second to -62544967141. Note that -62544967141 lies in the round one-minute interval -62544967200 ..< -62544967140.0. So when CF adds one second to -62544967141, it gets -62544967140, which would be in the next round one-minute interval. Since you specified wrapping components, CF isn't allowed to change the minute part of the date, so it wraps back to the beginning of the original round one-minute interval, -62544967200.
Finally, CF converts the ICU time back to a CF time, adding in the fractional part of the original time:
*atp = (udate / 1000.0) - kCFAbsoluteTimeIntervalSince1970 + startingFrac + (nanosecond * 1.0e-9);
So it returns -62544967200 + -0.9000015258789062 = -62544967200.9, exactly 59 seconds earlier than the input time.
Am I doing something wrong?
No, the bug is in CF, not in your code.
Is there any issue with my workaround?
If you don't need sub-second precision, your workaround should be fine.
I can reproduce it with more recent dates but so far only with negative reference dates, e.g. Date(timeIntervalSinceReferenceDate: -1008899941.9), which is 1969-01-11T22:00:58Z.
Any negative timeIntervalSinceReferenceDate in the last second of a minute interval should cause the problem. The bug effectively makes the first round whole minute prior to time 0 span from -60.99999999999999 through -1.0, but it should span from -60.0 through -5e324. All more-negative round minute intervals are similarly offset.

java.time.Instant.plus(long amountToAdd, TemporalUnit unit) Unsupported unit

I trying to add few years to current time. My code looks like:
// ten yeas ago
int backYears = 10;
Instant instant = ChronoUnit.YEARS.addTo(Instant.now(), -backYears);
But I got an exception:
java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Years
at java.time.Instant.plus(Instant.java:862)
When I opened the method Instant.plus I see the following:
#Override
public Instant plus(long amountToAdd, TemporalUnit unit) {
if (unit instanceof ChronoUnit) {
switch ((ChronoUnit) unit) {
case NANOS: return plusNanos(amountToAdd);
case MICROS: return plus(amountToAdd / 1000_000, (amountToAdd % 1000_000) * 1000);
case MILLIS: return plusMillis(amountToAdd);
case SECONDS: return plusSeconds(amountToAdd);
case MINUTES: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_MINUTE));
case HOURS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_HOUR));
case HALF_DAYS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_DAY / 2));
case DAYS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_DAY));
}
throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
}
return unit.addTo(this, amountToAdd);
}
As you can see MONTHS and YEARS are unsupported. But why?
With an old java.util.Calendar I can do that easily:
Calendar c = Calendar.getInstance();
c.setTime(date);
c.add(Calendar.YEAR, amount);
return c.getTime();
The only one reason what I guess is that we don't know how many days in a month and year because of leap day 29 Feb.
But to be honest we also have a leap second.
Thus I think that this is a bug and all ChronoUnits should be supported.
The only one question is: do we need to take in account leap second and leap day.
As for my needs it's okay just to assume that month has 30 days and year 365.
I don't need to make something like Calendar.roll() but this can satisfy me too.
Let’s try something out. I am taking an instant as ZonedDateTime and subtracting 10 years in different time zones.
OffsetDateTime origin = OffsetDateTime.of(2018, 3, 1, 0, 0, 0, 0, ZoneOffset.UTC);
Instant originInstant = origin.toInstant();
Instant tenYearsBackKyiv = origin.atZoneSameInstant(ZoneId.of("Europe/Kiev"))
.minusYears(10)
.toInstant();
long hoursSubtractedKyiv = ChronoUnit.HOURS.between(tenYearsBackKyiv, originInstant);
System.out.println("Hours subtracted in Київ: " + hoursSubtractedKyiv);
Instant tenYearsBackSaoPaulo = origin.atZoneSameInstant(ZoneId.of("America/Sao_Paulo"))
.minusYears(10)
.toInstant();
long hoursSubtractedSaoPaulo = ChronoUnit.HOURS.between(tenYearsBackSaoPaulo, originInstant);
System.out.println("Hours subtracted in São Paulo: " + hoursSubtractedSaoPaulo);
The output is:
Hours subtracted in Київ: 87648
Hours subtracted in São Paulo: 87672
As you can see, 24 hours more (1 day more) is subtracted in São Paulo compared to Київ (Kyiv, Kiev). You may already have figured out that it’s because there we pass from 1 March to 29 February three times in leap years, in Київ only twice.
The old and now outdated Calendar class always had a time zone in it, so knew in which time zone to subtract years (another thing is it was happy to give you a result even in situations where it was unclear which result you wanted). The modern classes ZonedDateTime, OffsetDateTime and LocalDateTime can do the same. So use them. An Instant conceptually doesn’t have a time zone, so refuses to do operations that depend on time zone (I know it’s implemented using UTC, but we should regard this as an irrelevant implementation detail, not as a part of the specification of the interface to the class).
Neither the old nor the modern classes take leap seoncds into account, and you are right, only therefore can an Instant add and subtract days, hours and minutes.

Getting time range between the first day of current week and current time JDK 8

I can easilly calculate time period between the first day of month and current time:
/**
* Returns the time range between the first day of month and current time in milliseconds.
*
* #param zoneId time zone ID.
* #return a {#code long} array, where at index: 0 - the first day of month midnight time; 1 - current time.
*/
public static long[] monthDateRange(ZoneId zoneId) {
long[] toReturn = new long[2];
ZonedDateTime nowZdt = LocalDateTime.now().atZone(zoneId);
ZonedDateTime startZdt = nowZdt.withDayOfMonth(1);
toReturn[0] = startZdt.toInstant().toEpochMilli();
toReturn[1] = nowZdt.toInstant().toEpochMilli();
return toReturn;
}
But how to start counting at the first day (midnight) of current week?
tl;dr
ZonedDateTime
.now( ZoneId.of( "Asia/Kolkata" ) ) // Current moment in a particular time zone.
.toLocalDate() // Extract date-only value, losing the time-of-day and time zone components.
.with( TemporalAdjusters.previousOrSame( DayOfWeek.SUNDAY ) ) // Move to another day-of-week, or same date if this is the desired day-of-week.
.atStartOfDay( ZoneId.of( "Asia/Kolkata" ) ) // Determine the first moment of the day. Do *not* assume this time-of-day is 00:00:00 as anomalies such as Daylight Saving Time (DST) may mean otherwise such as 01:00:00.
.toInstant() // Adjust into UTC, same moment, same point on the timeline, but viewed through the lens of UTC time zone.
.toEpochMilli() // Extract a count-from-epoch in milliseconds. I do *not* recommend tracking date-time this way, but the Question requires this number.
Details
The Answer by Gruodis is good, but here's an alternative that is a bit more direct and flexible.
Get current moment as a ZonedDateTime.
ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;
ZonedDateTime now = ZonedDateTime.now( z ) ;
TemporalAdjuster
The TemporalAdjuster interface lets you manipulate a date-time value to get a fresh date-time value. The TemporalAdjusters class (note plural s) provides several handy implementations. Use the DayOfWeek enum to specify what day you consider to be the first day of the week.
DayOfWeek dowStartOfWeek = DayOfWeek.MONDAY ;
LocalDate weekStartDate = now.toLocalDate().with( TemporalAdjusters.previousOrSame( DayOfWeek.MONDAY ) ) ;
ZonedDateTime start = weekStartDate.atStartOfDay( z ) ; // Determine first moment of the day. Note: *not* always 00:00:00.
See this code run live at IdeOne.com.
2017-08-21T00:00+12:00[Pacific/Auckland]
2017-08-21T08:44:46.439+12:00[Pacific/Auckland]
Span of time
To report your span of time, pou could indeed extract a count-from-epoch of whole seconds, if required.
long epochSeconds = start.toEpochSecond() ;
Or extract milliseconds via Instant.
long epochMillis = start.toInstant().toEpochMilli() ;
But keep in mind that both those numbers truncate any further fractional second, as the java.time types resolve to nanoseconds.
Besides truncation, there are other reasons to avoid tracking date-time as a count-from-epoch. Since such values are meaningless to the human eye, debugging is much more difficult and faulty data may escape your notice. Also, you may assume the epoch is 1970-01-01T00:00:00Z, but there are at least another couple dozen epochs is use by common software systems. Yet another problem is ambiguity over the granularity of the count, where some systems use whole seconds, others use milliseconds, others use microseconds, others nanoseconds, and still others use other resolutions.
Interval
So instead of returning mere long integer numbers, I suggest returning an object. A pair of Instant objects work, which is what is used by the Interval class in the ThreeTen-Extra project. That class has several very handy methods I expect the calling code may find useful such as contains, encloses, abuts, overlaps, span, isEmpty, and more.
org.threeten.extra.Interval interval = Interval.of( start.toInstant() , now.toInstant() ) ;
You can apply a time zone to view either the beginning or ending through the lens of a region’s own wall-clock time.
ZonedDateTime zdtStart = interval.getStart().atZone( z ); // Or `getEnd()`.
The solution:
/**
* Returns the time range between the first day of current week midnight and current time in milliseconds.
*
* #param zoneId time zone ID.
* #return a {#code long} array, where at index: 0 - the first day of current week midnight time; 1 - current time.
*/
public static long[] monthDateRange(ZoneId zoneId) {
long[] toReturn = new long[2];
//ZonedDateTime nowZdt = LocalDateTime.now().atZone(zoneId);
ZonedDateTime nowZdt = ZonedDateTime.now(zoneId);//As suggested by Basil Bourque (tested).
//ZonedDateTime startZdt = nowZdt.with(ChronoField.DAY_OF_WEEK, 1);
ZonedDateTime startZdt = nowZdt.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));//As suggested by Basil Bourque (tested).
startZdt = startZdt.toLocalDate ().atStartOfDay(zoneId);
toReturn[0] = startZdt.toInstant().toEpochMilli();
toReturn[1] = nowZdt.toInstant().toEpochMilli();
return toReturn;
}
See this code run live at IdeOne.com.

method for converting seconds from date to datetime

Is there a method in matlab to convert seconds from a known date to a standard date time format?
For example, if I have a vector of values shown as seconds from 1901/01/01, how would I convert them to a dateTime? In this case a value of 28125 would correspond to 1981/01/01. Is there an efficient method for doing this?
The numbers in your example do not make sense so it is not clear if your time is in seconds or days but since you asked for seconds I will use this.
What you want to achieve can be done using datenum function. This function returns the number of (fractional) days from 1/1/0000. So first you need to find your offset, e.g.:
offsetInDays = datenum(1901,1,1);
Next, you convert the date from seconds to days:
dateInDays = YourRequiredDateInSec * 3600 * 24;
Finally, you date is given by
RequiredDate = datestr(offsetInDays + dateInDays);