Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package io.github.md5sha256.realty.api;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.time.Duration;
import java.time.LocalDateTime;

public final class DurationFormatter {

Expand Down Expand Up @@ -57,4 +59,27 @@ private DurationFormatter() {}
}
return sb.toString();
}

/**
* Formats the remaining time until the given end date.
*
* <p>Returns {@code "N/A"} when no end date exists and {@code "Expired"}
* when the end date has already passed.</p>
*/
public static @NotNull String formatTimeLeft(@Nullable LocalDateTime endDate) {
return formatTimeLeft(endDate, LocalDateTime.now());
}

static @NotNull String formatTimeLeft(@Nullable LocalDateTime endDate, @NotNull LocalDateTime now) {
if (endDate == null) {
return "N/A";
}

Duration remaining = Duration.between(now, endDate);
if (remaining.isZero() || remaining.isNegative()) {
return "Expired";
}

return format(remaining);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,7 @@ public record RegionInfo(
placeholders.put("duration", DurationFormatter.format(Duration.ofSeconds(lease.durationSeconds())));
placeholders.put("start_date", lease.startDate() != null ? dateFormatter.apply(lease.startDate()) : "N/A");
placeholders.put("end_date", lease.endDate() != null ? dateFormatter.apply(lease.endDate()) : "N/A");
placeholders.put("time_left", DurationFormatter.formatTimeLeft(lease.endDate()));
if (lease.maxExtensions() != null) {
placeholders.put("extensions", lease.currentMaxExtensions() + "/" + lease.maxExtensions());
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.github.md5sha256.realty.api;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.time.LocalDateTime;

class DurationFormatterTest {

@Test
@DisplayName("formatTimeLeft returns N/A when no end date exists")
void noEndDate() {
String result = DurationFormatter.formatTimeLeft(null, LocalDateTime.of(2026, 3, 25, 1, 0, 0));
Assertions.assertEquals("N/A", result);
}

@Test
@DisplayName("formatTimeLeft returns Expired when end date has passed")
void expired() {
String result = DurationFormatter.formatTimeLeft(
LocalDateTime.of(2026, 3, 25, 1, 0, 0),
LocalDateTime.of(2026, 3, 25, 1, 0, 1)
);
Assertions.assertEquals("Expired", result);
}

@Test
@DisplayName("formatTimeLeft formats remaining time using the standard duration formatter")
void remainingTime() {
String result = DurationFormatter.formatTimeLeft(
LocalDateTime.of(2026, 3, 27, 4, 30, 15),
LocalDateTime.of(2026, 3, 25, 1, 0, 0)
);
Assertions.assertEquals("2d 3h 30m 15s", result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ private void appendLeaseholdInfo(@NotNull TextComponent.Builder builder,
Placeholder.unparsed("end_date", leasehold.endDate() != null
? DateFormatter.format(settings.get(), leasehold.endDate())
: "N/A"),
Placeholder.unparsed("time_left", DurationFormatter.formatTimeLeft(leasehold.endDate())),
Placeholder.unparsed("extensions", extensions)));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.github.md5sha256.realty.command;

import io.github.md5sha256.realty.api.DurationFormatter;
import io.github.md5sha256.realty.database.RealtyLogicImpl;
import io.github.md5sha256.realty.database.entity.LeaseholdContractEntity;
import io.github.md5sha256.realty.database.entity.RealtyRegionEntity;
import io.github.md5sha256.realty.localisation.MessageContainer;
import io.github.md5sha256.realty.localisation.MessageKeys;
Expand Down Expand Up @@ -137,7 +139,7 @@ private void listAll(@NotNull CommandSender sender, @NotNull UUID targetId,
builder.append(parseMiniMessage(MessageKeys.LIST_HEADER, "<player>", targetName));
appendCategory(builder, "Owned", result.owned());
appendCategory(builder, "Landlord", result.landlord());
appendCategory(builder, "Rented", result.rented());
appendRentedCategory(builder, "Rented", result.rented());
appendFooter(builder, targetName, null, page, totalPages);
sender.sendMessage(builder.build());
}
Expand Down Expand Up @@ -165,7 +167,11 @@ private void listCategory(@NotNull CommandSender sender, @NotNull UUID targetId,
String label = "owned".equals(category) ? "Owned" : "Rented";
TextComponent.Builder builder = Component.text();
builder.append(parseMiniMessage(MessageKeys.LIST_HEADER, "<player>", targetName));
appendCategory(builder, label, result.regions());
if ("owned".equals(category)) {
appendCategory(builder, label, result.regions());
} else {
appendRentedCategory(builder, label, result.regions());
}
appendFooter(builder, targetName, category, page, totalPages);
sender.sendMessage(builder.build());
}
Expand All @@ -183,6 +189,25 @@ private void appendCategory(@NotNull TextComponent.Builder builder, @NotNull Str
}
}

private void appendRentedCategory(@NotNull TextComponent.Builder builder, @NotNull String label,
@NotNull List<RealtyRegionEntity> regions) {
if (regions.isEmpty()) {
return;
}
builder.appendNewline()
.append(parseMiniMessage(MessageKeys.LIST_CATEGORY, "<label>", label));
for (RealtyRegionEntity region : regions) {
LeaseholdContractEntity leasehold = logic.getLeaseholdContract(region.worldGuardRegionId(), region.worldId());
String timeLeft = DurationFormatter.formatTimeLeft(leasehold != null ? leasehold.endDate() : null);
builder.appendNewline()
.append(parseMiniMessage(
MessageKeys.LIST_RENTED_ENTRY,
"<region>", region.worldGuardRegionId(),
"<time_left>", timeLeft
));
}
}

private void appendFooter(@NotNull TextComponent.Builder builder, @NotNull String targetName,
@Nullable String category, int page, int totalPages) {
Component previousComponent = page > 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ private MessageKeys() {}
public static final String LIST_HEADER = "list.header";
public static final String LIST_CATEGORY = "list.category";
public static final String LIST_ENTRY = "list.entry";
public static final String LIST_RENTED_ENTRY = "list.rented-entry";
public static final String LIST_FOOTER = "list.footer";
public static final String LIST_PREVIOUS = "list.previous";
public static final String LIST_NEXT = "list.next";
Expand Down
2 changes: 2 additions & 0 deletions realty-paper/src/main/resources/messages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ info:
- "Duration: <duration>"
- "Start Date: <start_date>"
- "End Date: <end_date>"
- "Time Left: <time_left>"
- "Extensions: <extensions>"
auction-active: "<#00aaff>►</#00aaff> Active Auction: <yellow><has_auction></yellow>"
error: "Failed to retrieve region info: <error>"
Expand Down Expand Up @@ -161,6 +162,7 @@ list:
header: "--- Regions for <player> ---"
category: "<label>:"
entry: " - <region>"
rented-entry: " - <region> <gray>(<time_left>)</gray>"
footer: "Page <page> of <total> <previous><next>"
previous: "<click:run_command:<command>><yellow>< Previous</yellow></click> "
next: " <click:run_command:<command>><yellow>Next ></yellow></click>"
Expand Down
2 changes: 1 addition & 1 deletion realty-paper/src/main/resources/profiles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
# - "<dark_red><bold>[Leased]"
# - "<region>"
# - "<tenant>"
# - "<end_date>"
# - "<time_left>"
# left-click-commands:
# - "realty info <region>"
# grouped:
Expand Down