@@ -97,55 +97,64 @@ private static List<DailyValue> fetchDailyValues(String timeSeriesId, String sta
9797 }
9898
9999 /**
100- * Fetches continuous values for a date range, paging in chunks when necessary.
100+ * Fetches continuous values for a date range, paging in chunks when necessary.
101101 */
102102 private static List <InstantaneousValue > fetchContinuousValues (String timeSeriesId , String startDate , String endDate ) throws Exception {
103103 if (TestSite .isTestSeriesId (timeSeriesId ))
104104 return TestSite .generateContinuousValues (startDate , endDate );
105105
106- List <InstantaneousValue > all = new ArrayList <>();
107106 Instant rangeEnd = Instant .parse (normalizeToInstant (endDate ));
108107 Instant chunkStart = Instant .parse (normalizeToInstant (startDate ));
108+
109+ List <InstantaneousValue > all = new ArrayList <>();
109110 Instant lastSeen = null ;
110- boolean continuation = false ;
111111 while (chunkStart .isBefore (rangeEnd )) {
112- // Cap the request at the API's maximum time envelope.
113112 Instant windowEnd = chunkStart .plus (MAX_WINDOW_DAYS , ChronoUnit .DAYS );
114113 if (windowEnd .isAfter (rangeEnd )) windowEnd = rangeEnd ;
115114
116- String url = String .format (CONTINUOUS_URL_ID , timeSeriesId , chunkStart .toString (), windowEnd .toString ());
117- String csv = WebUtility .getPage (url );
118- List <InstantaneousValue > page = (csv == null || csv .isBlank ())
119- ? Collections .emptyList ()
120- : CsvFile .fromString (csv ).mapRows (InstantaneousValue ::fromRow );
121-
122- for (int i = 0 ; i < page .size (); i ++) {
123- InstantaneousValue iv = page .get (i );
124- // A continuation query is inclusive of its start, so its first point repeats
125- // the previous chunk's last point. Drop that one expected boundary duplicate.
126- if (continuation && i == 0 && iv .time .equals (lastSeen )) continue ;
127- // Any other repeated time-stamp would break a database save. Keep the first
128- // value for that time, drop the rest, and warn so it can be investigated.
129- if (lastSeen != null && !iv .time .isAfter (lastSeen )) {
130- logger .warning ("Dropping duplicate time-stamp " + iv .time
131- + " in continuous series " + timeSeriesId );
132- continue ;
133- }
134- all .add (iv );
135- lastSeen = iv .time ;
136- }
137- continuation = true ;
115+ List <InstantaneousValue > page = fetchContinuousPage (timeSeriesId , chunkStart , windowEnd );
116+ lastSeen = appendDeduplicated (all , page , lastSeen , timeSeriesId );
138117
139118 if (page .size () >= PAGE_LIMIT && lastSeen != null && lastSeen .isAfter (chunkStart )) {
140- // Window was truncated at the page limit; resume from its last point.
119+ // Response was truncated at the page limit; resume from its last point.
141120 chunkStart = lastSeen ;
142121 logger .info ("Continuous query returned a full page; fetching next chunk from " + chunkStart );
143122 } else {
144- // Window exhausted; advance to the next time window.
145123 chunkStart = windowEnd ;
146124 }
147125 }
148- return all .isEmpty () ? Collections .emptyList () : all ;
126+ return all ;
127+ }
128+
129+ /**
130+ * Fetches a single page of continuous values for one time window.
131+ */
132+ private static List <InstantaneousValue > fetchContinuousPage (String timeSeriesId , Instant start , Instant end ) throws Exception {
133+ String url = String .format (CONTINUOUS_URL_ID , timeSeriesId , start .toString (), end .toString ());
134+ String csv = WebUtility .getPage (url );
135+ if (csv == null || csv .isBlank ()) return Collections .emptyList ();
136+ return CsvFile .fromString (csv ).mapRows (InstantaneousValue ::fromRow );
137+ }
138+
139+ /**
140+ * Appends a page's values to dest, skipping potental duplicates at the page boundary.
141+ */
142+ private static Instant appendDeduplicated (List <InstantaneousValue > dest , List <InstantaneousValue > page ,
143+ Instant lastSeen , String timeSeriesId ) {
144+ for (int i = 0 ; i < page .size (); i ++) {
145+ InstantaneousValue iv = page .get (i );
146+ if (lastSeen != null && !iv .time .isAfter (lastSeen )) {
147+ boolean expectedBoundaryDuplicate = i == 0 && iv .time .equals (lastSeen );
148+ if (!expectedBoundaryDuplicate ) {
149+ logger .warning ("Dropping duplicate time-stamp " + iv .time
150+ + " in continuous series " + timeSeriesId );
151+ }
152+ continue ;
153+ }
154+ dest .add (iv );
155+ lastSeen = iv .time ;
156+ }
157+ return lastSeen ;
149158 }
150159
151160 /**
0 commit comments