44import static org .junit .jupiter .api .Assertions .assertNotNull ;
55import static org .junit .jupiter .api .Assertions .assertTrue ;
66
7+ import java .util .concurrent .TimeUnit ;
8+
79import org .junit .jupiter .api .DisplayName ;
810import org .junit .jupiter .api .Test ;
911
1012import com .bencodez .simpleapi .time .ParsedDuration ;
1113import com .bencodez .simpleapi .time .ParsedDuration .Unit ;
1214
15+ /**
16+ * Unit tests for {@link ParsedDuration}.
17+ */
1318public class ParsedDurationTest {
1419
20+ // ---------------------------------------------------------------------------------
21+ // Shared/basic behavior (null/empty, tokens, ISO) for BOTH Unit and TimeUnit APIs
22+ // ---------------------------------------------------------------------------------
23+
1524 @ Test
16- @ DisplayName ("Empty and null inputs return empty duration" )
25+ @ DisplayName ("Empty and null inputs return empty duration (Unit + TimeUnit) " )
1726 public void testEmptyInputs () {
1827 assertTrue (ParsedDuration .parse (null ).isEmpty ());
1928 assertTrue (ParsedDuration .parse ("" ).isEmpty ());
2029 assertTrue (ParsedDuration .parse (" " ).isEmpty ());
21- }
2230
23- @ Test
24- @ DisplayName ("Plain number uses default MINUTES" )
25- public void testPlainNumberDefaultMinutes () {
26- ParsedDuration d = ParsedDuration .parse ("30" );
27- assertEquals (30 * 60_000L , d .getMillis ());
28- }
31+ assertTrue (ParsedDuration .parse (null , Unit .MINUTES ).isEmpty ());
32+ assertTrue (ParsedDuration .parse ("" , Unit .MINUTES ).isEmpty ());
33+ assertTrue (ParsedDuration .parse (" " , Unit .MINUTES ).isEmpty ());
2934
30- @ Test
31- @ DisplayName ("Milliseconds parsing" )
32- public void testMilliseconds () {
33- ParsedDuration d = ParsedDuration .parse ("5000ms" );
34- assertEquals (5000L , d .getMillis ());
35+ assertTrue (ParsedDuration .parse (null , TimeUnit .MINUTES ).isEmpty ());
36+ assertTrue (ParsedDuration .parse ("" , TimeUnit .MINUTES ).isEmpty ());
37+ assertTrue (ParsedDuration .parse (" " , TimeUnit .MINUTES ).isEmpty ());
3538 }
3639
3740 @ Test
38- @ DisplayName ("Seconds parsing" )
39- public void testSeconds () {
40- ParsedDuration d = ParsedDuration .parse ("60s" );
41- assertEquals (60_000L , d .getMillis ());
42- }
41+ @ DisplayName ("ISO-8601 duration parsing (PT/P1D) works the same (Unit + TimeUnit) " )
42+ public void testIso8601Duration () {
43+ assertEquals ( 30 * 60_000L , ParsedDuration .parse ("PT30M" , Unit . MINUTES ). getMillis () );
44+ assertEquals (12 * 3_600_000L , ParsedDuration . parse ( "PT12H" , Unit . MINUTES ) .getMillis ());
45+ assertEquals ( 86_400_000L , ParsedDuration . parse ( "P1D" , Unit . MINUTES ). getMillis ());
4346
44- @ Test
45- @ DisplayName ("Minutes parsing" )
46- public void testMinutes () {
47- ParsedDuration d = ParsedDuration .parse ("30m" );
48- assertEquals (30 * 60_000L , d .getMillis ());
47+ assertEquals (30 * 60_000L , ParsedDuration .parse ("PT30M" , TimeUnit .MINUTES ).getMillis ());
48+ assertEquals (12 * 3_600_000L , ParsedDuration .parse ("PT12H" , TimeUnit .MINUTES ).getMillis ());
49+ assertEquals (86_400_000L , ParsedDuration .parse ("P1D" , TimeUnit .MINUTES ).getMillis ());
4950 }
5051
5152 @ Test
52- @ DisplayName ("Hours parsing " )
53- public void testHours () {
54- ParsedDuration d = ParsedDuration .parse ("12h" );
55- assertEquals ( 12 * 3_600_000L , d . getMillis ());
53+ @ DisplayName ("ISO-8601 months are rejected (P1M) for both APIs " )
54+ public void testIsoMonthsRejected () {
55+ assertTrue ( ParsedDuration .parse ("P1M" , Unit . MINUTES ). isEmpty () );
56+ assertTrue ( ParsedDuration . parse ( "P1M" , TimeUnit . MINUTES ). isEmpty ());
5657 }
5758
5859 @ Test
59- @ DisplayName ("Days parsing" )
60- public void testDays () {
61- ParsedDuration d = ParsedDuration .parse ("1d" );
62- assertEquals (86_400_000L , d .getMillis ());
60+ @ DisplayName ("Fixed month tokens behave the same (3mo, 1mo2d) for both APIs" )
61+ public void testFixedMonthTokens () {
62+ long expected3mo = 3L * 30L * 86_400_000L ;
63+ assertEquals (expected3mo , ParsedDuration .parse ("3mo" , Unit .MINUTES ).getMillis ());
64+ assertEquals (expected3mo , ParsedDuration .parse ("3mo" , TimeUnit .MINUTES ).getMillis ());
65+
66+ long expected = 1L * 30L * 86_400_000L + 2L * 86_400_000L ;
67+ assertEquals (expected , ParsedDuration .parse ("1mo2d" , Unit .MINUTES ).getMillis ());
68+ assertEquals (expected , ParsedDuration .parse ("1mo2d" , TimeUnit .MINUTES ).getMillis ());
6369 }
6470
6571 @ Test
66- @ DisplayName ("Weeks parsing" )
67- public void testWeeks () {
68- ParsedDuration d = ParsedDuration .parse ("2w" );
69- assertEquals (2 * 604_800_000L , d .getMillis ());
72+ @ DisplayName ("Combined tokens allow spaces (2d 12h) for both APIs" )
73+ public void testCombinedWithSpaces () {
74+ long expected = 2 * 86_400_000L + 12 * 3_600_000L ;
75+ assertEquals (expected , ParsedDuration .parse ("2d 12h" , Unit .MINUTES ).getMillis ());
76+ assertEquals (expected , ParsedDuration .parse ("2d 12h" , TimeUnit .MINUTES ).getMillis ());
7077 }
7178
7279 @ Test
73- @ DisplayName ("Months parsing is fixed 30-day millis (3mo)" )
74- public void testMonthsFixedMillis () {
75- ParsedDuration d = ParsedDuration .parse ("3mo" );
76- assertEquals (3L * 30L * 86_400_000L , d .getMillis ());
80+ @ DisplayName ("Garbage input returns empty for both APIs" )
81+ public void testGarbageInput () {
82+ assertTrue (ParsedDuration .parse ("abc" , Unit .MINUTES ).isEmpty ());
83+ assertTrue (ParsedDuration .parse ("!@#$" , Unit .MINUTES ).isEmpty ());
84+
85+ assertTrue (ParsedDuration .parse ("abc" , TimeUnit .MINUTES ).isEmpty ());
86+ assertTrue (ParsedDuration .parse ("!@#$" , TimeUnit .MINUTES ).isEmpty ());
7787 }
7888
7989 @ Test
80- @ DisplayName ("Combined tokens can include months as fixed millis: 1mo2d" )
81- public void testCombinedMonthsPlusDaysFixed () {
82- ParsedDuration d = ParsedDuration .parse ("1mo2d" );
83- assertEquals (1L * 30L * 86_400_000L + 2L * 86_400_000L , d .getMillis ());
90+ @ DisplayName ("Zero or negative values return empty for both APIs" )
91+ public void testZeroAndNegative () {
92+ assertTrue (ParsedDuration .parse ("0m" , Unit .MINUTES ).isEmpty ());
93+ assertTrue (ParsedDuration .parse ("-5m" , Unit .MINUTES ).isEmpty ());
94+
95+ assertTrue (ParsedDuration .parse ("0m" , TimeUnit .MINUTES ).isEmpty ());
96+ assertTrue (ParsedDuration .parse ("-5m" , TimeUnit .MINUTES ).isEmpty ());
8497 }
8598
99+ // ---------------------------------------------------------------------------------
100+ // Unit-specific behavior tests
101+ // ---------------------------------------------------------------------------------
102+
86103 @ Test
87- @ DisplayName ("Combined tokens allow spaces: 2d 12h " )
88- public void testCombinedWithSpaces () {
89- ParsedDuration d = ParsedDuration .parse ("2d 12h " );
90- assertEquals (2 * 86_400_000L + 12 * 3_600_000L , d .getMillis ());
104+ @ DisplayName ("Unit API: plain number uses default MINUTES " )
105+ public void testUnitPlainNumberDefaultMinutes () {
106+ ParsedDuration d = ParsedDuration .parse ("30 " ); // default Unit.MINUTES
107+ assertEquals (30 * 60_000L , d .getMillis ());
91108 }
92109
93110 @ Test
94- @ DisplayName ("Combined tokens: 1w2d3h4m5s6ms" )
95- public void testCombinedManySegments () {
96- ParsedDuration d = ParsedDuration .parse ("1w2d3h4m5s6ms" );
97- long expected = 1 * 604_800_000L + 2 * 86_400_000L + 3 * 3_600_000L + 4 * 60_000L + 5 * 1000L + 6 ;
98- assertEquals (expected , d .getMillis ());
111+ @ DisplayName ("Unit API: plain number respects custom default Unit" )
112+ public void testUnitPlainNumberCustomDefault () {
113+ assertEquals (15_000L , ParsedDuration .parse ("15" , Unit .SECONDS ).getMillis ());
114+ assertEquals (2 * 3_600_000L , ParsedDuration .parse ("2" , Unit .HOURS ).getMillis ());
99115 }
100116
101117 @ Test
102- @ DisplayName ("Plain number respects custom default unit" )
103- public void testPlainNumberCustomUnit () {
104- ParsedDuration seconds = ParsedDuration .withDefaultUnit ("15" , Unit .SECONDS );
105- assertEquals (15_000L , seconds .getMillis ());
106-
107- ParsedDuration hours = ParsedDuration .withDefaultUnit ("2" , Unit .HOURS );
108- assertEquals (2 * 3_600_000L , hours .getMillis ());
118+ @ DisplayName ("Unit API: unknown suffix falls back to provided default Unit (single token)" )
119+ public void testUnitUnknownSuffixFallbackSingle () {
120+ // default = minutes
121+ assertEquals (10 * 60_000L , ParsedDuration .parse ("10x" , Unit .MINUTES ).getMillis ());
122+ // default = seconds
123+ assertEquals (10 * 1000L , ParsedDuration .parse ("10x" , Unit .SECONDS ).getMillis ());
109124 }
110125
111126 @ Test
112- @ DisplayName ("ISO-8601 duration parsing (PT)" )
113- public void testIso8601Duration () {
114- ParsedDuration d1 = ParsedDuration .parse ("PT30M" );
115- assertEquals (30 * 60_000L , d1 .getMillis ());
127+ @ DisplayName ("Unit API: unknown suffix falls back to provided default Unit (combined tokens)" )
128+ public void testUnitUnknownSuffixFallbackCombined () {
129+ // 30x should fallback to minutes (default unit = MINUTES)
130+ long expectedMinutesFallback = 1 * 3_600_000L + 30 * 60_000L ;
131+ assertEquals (expectedMinutesFallback , ParsedDuration .parse ("1h30x" , Unit .MINUTES ).getMillis ());
116132
117- ParsedDuration d2 = ParsedDuration .parse ("PT12H" );
118- assertEquals (12 * 3_600_000L , d2 .getMillis ());
119-
120- ParsedDuration d3 = ParsedDuration .parse ("P1D" );
121- assertEquals (86_400_000L , d3 .getMillis ());
133+ // 30x should fallback to seconds (default unit = SECONDS)
134+ long expectedSecondsFallback = 1 * 3_600_000L + 30 * 1000L ;
135+ assertEquals (expectedSecondsFallback , ParsedDuration .parse ("1h30x" , Unit .SECONDS ).getMillis ());
122136 }
123137
138+ // ---------------------------------------------------------------------------------
139+ // TimeUnit-specific behavior tests
140+ // ---------------------------------------------------------------------------------
141+
124142 @ Test
125- @ DisplayName ("ISO-8601 months are rejected (P1M) " )
126- public void testIsoMonthsRejected () {
127- ParsedDuration d = ParsedDuration .parse ("P1M" );
128- assertTrue ( d . isEmpty (), "ISO months should not be parsed as minutes or months" );
143+ @ DisplayName ("TimeUnit API: plain number respects custom default TimeUnit " )
144+ public void testTimeUnitPlainNumberCustomDefault () {
145+ assertEquals ( 15_000L , ParsedDuration .parse ("15" , TimeUnit . SECONDS ). getMillis () );
146+ assertEquals ( 2 * 3_600_000L , ParsedDuration . parse ( "2" , TimeUnit . HOURS ). getMillis () );
129147 }
130148
131149 @ Test
132- @ DisplayName ("Unknown suffix falls back to default unit" )
133- public void testUnknownSuffixFallback () {
134- ParsedDuration d = ParsedDuration .parse ("10x" );
135- assertEquals (10 * 60_000L , d .getMillis ()); // default = minutes
150+ @ DisplayName ("TimeUnit API: unknown suffix falls back to provided default TimeUnit (single token)" )
151+ public void testTimeUnitUnknownSuffixFallbackSingle () {
152+ // default = minutes
153+ assertEquals (10 * 60_000L , ParsedDuration .parse ("10x" , TimeUnit .MINUTES ).getMillis ());
154+ // default = seconds
155+ assertEquals (10 * 1000L , ParsedDuration .parse ("10x" , TimeUnit .SECONDS ).getMillis ());
136156 }
137157
138158 @ Test
139- @ DisplayName ("Unknown suffix inside combined tokens falls back to default unit" )
140- public void testUnknownSuffixFallbackCombined () {
141- ParsedDuration d = ParsedDuration .parse ("1h30x" ); // 30x => 30 minutes by default
142- assertEquals (1 * 3_600_000L + 30 * 60_000L , d .getMillis ());
159+ @ DisplayName ("TimeUnit API: unknown suffix falls back to provided default TimeUnit (combined tokens)" )
160+ public void testTimeUnitUnknownSuffixFallbackCombined () {
161+ // 30x should fallback to minutes (default timeunit = MINUTES)
162+ long expectedMinutesFallback = 1 * 3_600_000L + 30 * 60_000L ;
163+ assertEquals (expectedMinutesFallback , ParsedDuration .parse ("1h30x" , TimeUnit .MINUTES ).getMillis ());
164+
165+ // 30x should fallback to seconds (default timeunit = SECONDS)
166+ long expectedSecondsFallback = 1 * 3_600_000L + 30 * 1000L ;
167+ assertEquals (expectedSecondsFallback , ParsedDuration .parse ("1h30x" , TimeUnit .SECONDS ).getMillis ());
143168 }
144169
145170 @ Test
146- @ DisplayName ("Garbage input returns empty" )
147- public void testGarbageInput () {
148- assertTrue (ParsedDuration .parse ("abc" ).isEmpty ());
149- assertTrue (ParsedDuration .parse ("!@#$" ).isEmpty ());
171+ @ DisplayName ("TimeUnit API: nanos/micros default units truncate to millis (number-only)" )
172+ public void testTimeUnitSubMillisDefaultsTruncate () {
173+ // 999,999ns -> 0ms => empty (because ParsedDuration clamps <=0 to empty)
174+ assertTrue (ParsedDuration .parse ("999999" , TimeUnit .NANOSECONDS ).isEmpty ());
175+
176+ // 1,500,000ns -> 1ms
177+ assertEquals (1L , ParsedDuration .parse ("1500000" , TimeUnit .NANOSECONDS ).getMillis ());
178+
179+ // 999us -> 0ms => empty
180+ assertTrue (ParsedDuration .parse ("999" , TimeUnit .MICROSECONDS ).isEmpty ());
181+
182+ // 1,500us -> 1ms
183+ assertEquals (1L , ParsedDuration .parse ("1500" , TimeUnit .MICROSECONDS ).getMillis ());
150184 }
151185
186+ // ---------------------------------------------------------------------------------
187+ // A few sanity checks for parsing suffixes are consistent across both APIs
188+ // ---------------------------------------------------------------------------------
189+
152190 @ Test
153- @ DisplayName ("Zero or negative values return empty" )
154- public void testZeroAndNegative () {
155- assertTrue (ParsedDuration .parse ("0m" ).isEmpty ());
156- assertTrue (ParsedDuration .parse ("-5m" ).isEmpty ());
157- assertTrue (ParsedDuration .withDefaultUnit ("0" , Unit .SECONDS ).isEmpty ());
191+ @ DisplayName ("Suffix parsing yields same millis regardless of default (Unit + TimeUnit)" )
192+ public void testSuffixParsingIndependentOfDefault () {
193+ assertEquals (5000L , ParsedDuration .parse ("5000ms" , Unit .HOURS ).getMillis ());
194+ assertEquals (5000L , ParsedDuration .parse ("5000ms" , TimeUnit .HOURS ).getMillis ());
195+
196+ assertEquals (60_000L , ParsedDuration .parse ("60s" , Unit .DAYS ).getMillis ());
197+ assertEquals (60_000L , ParsedDuration .parse ("60s" , TimeUnit .DAYS ).getMillis ());
198+
199+ assertEquals (12 * 3_600_000L , ParsedDuration .parse ("12h" , Unit .SECONDS ).getMillis ());
200+ assertEquals (12 * 3_600_000L , ParsedDuration .parse ("12h" , TimeUnit .SECONDS ).getMillis ());
158201 }
159202
160203 @ Test
@@ -163,7 +206,10 @@ public void testToString() {
163206 ParsedDuration empty = ParsedDuration .empty ();
164207 assertNotNull (empty .toString ());
165208
166- ParsedDuration d = ParsedDuration .parse ("5m" );
209+ ParsedDuration d = ParsedDuration .parse ("5m" , Unit . MINUTES );
167210 assertTrue (d .toString ().contains ("millis" ));
211+
212+ ParsedDuration dt = ParsedDuration .parse ("5m" , TimeUnit .MINUTES );
213+ assertTrue (dt .toString ().contains ("millis" ));
168214 }
169215}
0 commit comments