java
42 lines · 7 steps
Adding ISO-8601 durations across time zones in Java
A calculator that adds an ISO-8601 duration to an instant, handling calendar units through a zone so DST and month lengths stay correct.
Explained by
highlit
1package com.example.time;
2
3import java.time.Duration;
4import java.time.Instant;
5import java.time.LocalDateTime;
6import java.time.Period;
7import java.time.ZoneId;
8import java.time.format.DateTimeParseException;
9
10public final class DurationOffsetCalculator {
11
12 private final ZoneId zone;
13
14 public DurationOffsetCalculator(ZoneId zone) {
15 this.zone = zone;
16 }
17
18 public Instant addIso8601(Instant start, String iso8601) {
19 if (iso8601 == null || iso8601.isBlank()) {
20 throw new IllegalArgumentException("duration must not be blank");
21 }
22
23 int timeIndex = iso8601.indexOf('T');
24 String datePart = timeIndex < 0 ? iso8601 : iso8601.substring(0, timeIndex);
25 boolean hasCalendarUnits = datePart.length() > 1 || datePart.contains("Y") || datePart.contains("M") || datePart.contains("W") || datePart.contains("D");
26
27 try {
28 if (hasCalendarUnits) {
29 Period period = Period.parse(timeIndex < 0 ? iso8601 : datePart);
30 Duration time = timeIndex < 0 ? Duration.ZERO : Duration.parse("P" + iso8601.substring(timeIndex));
31 return LocalDateTime.ofInstant(start, zone)
32 .plus(period)
33 .plus(time)
34 .atZone(zone)
35 .toInstant();
36 }
37 return start.plus(Duration.parse(iso8601));
38 } catch (DateTimeParseException e) {
39 throw new IllegalArgumentException("invalid ISO-8601 duration: " + iso8601, e);
40 }
41 }
42}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Calendar units like months and days are zone-dependent, so they must be applied through a LocalDateTime rather than a fixed-length Duration.
- 2Splitting an ISO-8601 string at 'T' lets you route the date part to Period and the time part to Duration, which parse different halves.
- 3Wrapping parse calls to rethrow a domain-specific exception gives callers a clear, uniform failure contract.
Related explainers
ruby
class UserAgentParser BROWSERS = [ [/Edg\/([\d.]+)/, "Edge"], [/OPR\/([\d.]+)/, "Opera"],
Parsing user-agent strings in Ruby
regex
pattern-matching
lookup-tables
Intermediate
8 steps
java
@Component @Converter public class EncryptedStringConverter implements AttributeConverter<String, String> {
Transparent column encryption in Spring & JPA
encryption
aes-gcm
jpa-converter
Advanced
10 steps
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
java
package com.acme.billing.config; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties;
Feature-flagged beans with Spring @ConditionalOnProperty
feature-flags
conditional-beans
strategy-pattern
Intermediate
5 steps
rust
use chrono::{Duration, NaiveDate}; #[derive(Debug)] pub struct DateRange {
Parsing and iterating date ranges in Rust
error-handling
iterators
parsing
Intermediate
7 steps
java
public static Map<String, String> parseCookieHeader(String header) { Map<String, String> cookies = new LinkedHashMap<>(); if (header == null || header.isBlank()) { return cookies;
Parsing an HTTP Cookie header in Java
string-parsing
http
url-decoding
Intermediate
6 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/adding-iso-8601-durations-across-time-zones-in-java-explained-java-e550/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.