universal-parcel-scraper 0.652.1-main.3103 → 0.652.2-main.3105

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import { load } from 'cheerio';
2
+ import { DateTime, FixedOffsetZone } from 'luxon';
2
3
  import makeFetchCookie from 'fetch-cookie';
3
4
  import { CookieJar } from 'tough-cookie';
4
5
  import { carrierErrorKind, ChallengeError, IndeterminateError, InvalidInputError, NotFoundError, SchemaError, TransportError, UpstreamHttpError } from '../../core/errors/index.js';
@@ -295,30 +296,44 @@ export function parseUPSTrackingHtml(page, trackingNumber) {
295
296
  function text(value, maxLength) {
296
297
  return clean(typeof value === 'string' ? decodeEntities(value) : value, maxLength);
297
298
  }
299
+ /** UPS's offset of a scan's place, in minutes. */
300
+ function statedOffset(activity) {
301
+ const offset = /^([+-])(0\d|1[0-4]):([0-5]\d)$/.exec(cleanScalar(activity.gmtOffset));
302
+ return offset ? (offset[1] === '-' ? -1 : 1) * (Number(offset[2]) * 60 + Number(offset[3])) : null;
303
+ }
298
304
  /**
299
305
  * A scan's clock. UPS sends the same scan twice: a UTC pair (`gmtDate` /
300
- * `gmtTime`) and a local pair with a `gmtOffset`. Either becomes an
301
- * offset-carrying ISO string, so no zone is ever guessed. A local pair without
302
- * an offset stays a `local_time`, a scan UPS dates by its day alone, as it does
303
- * some drop-offs, keeps that day, other text stays as UPS wrote it, and a scan
304
- * with no clock at all, like some label scans, gets none.
306
+ * `gmtTime`) and a local pair with a `gmtOffset`. The UTC pair's instant, to
307
+ * the second, is written on that offset when the local pair reads the same
308
+ * minute on it, so the scan keeps its place's clock; otherwise it stays in
309
+ * UTC. A zero offset is written as UTC was, `+00:00`. Without the UTC pair,
310
+ * the local pair and its offset become the time, and no zone is ever guessed.
311
+ * A local pair without an offset stays a `local_time`, a scan UPS dates by
312
+ * its day alone, as it does some drop-offs, keeps that day, other text stays
313
+ * as UPS wrote it, and a scan with no clock at all, like some label scans,
314
+ * gets none.
305
315
  */
306
316
  function activityClock(activity) {
307
- const gmtDate = cleanScalar(activity.gmtDate);
308
- const gmtTime = cleanScalar(activity.gmtTime);
309
- if (/^\d{8}$/.test(gmtDate) && /^\d{2}:\d{2}:\d{2}$/.test(gmtTime)) {
310
- return { time: `${gmtDate.slice(0, 4)}-${gmtDate.slice(4, 6)}-${gmtDate.slice(6, 8)}T${gmtTime}+00:00` };
311
- }
312
317
  const localDate = cleanScalar(activity.date);
313
318
  const localTime = cleanScalar(activity.time);
314
319
  const dateMatch = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(localDate);
315
320
  const timeMatch = /^(\d{1,2}):([0-5]\d)\s*([AP])\.?M\.?$/i.exec(localTime);
316
321
  const hour = Number(timeMatch?.[1]);
317
- if (dateMatch && timeMatch && hour >= 1 && hour <= 12) {
318
- const hours = String(hour % 12 + (timeMatch[3].toUpperCase() === 'P' ? 12 : 0)).padStart(2, '0');
319
- const local = `${dateMatch[3]}-${dateMatch[1]}-${dateMatch[2]}T${hours}:${timeMatch[2]}:00`;
320
- const offset = cleanScalar(activity.gmtOffset);
321
- return /^[+-]\d{2}:\d{2}$/.test(offset) ? { time: `${local}${offset}` } : { local_time: local };
322
+ const local = dateMatch && timeMatch && hour >= 1 && hour <= 12
323
+ ? `${dateMatch[3]}-${dateMatch[1]}-${dateMatch[2]}T${String(hour % 12 + (timeMatch[3].toUpperCase() === 'P' ? 12 : 0)).padStart(2, '0')}:${timeMatch[2]}:00`
324
+ : null;
325
+ const offset = statedOffset(activity);
326
+ const gmtDate = cleanScalar(activity.gmtDate);
327
+ const gmtTime = cleanScalar(activity.gmtTime);
328
+ if (/^\d{8}$/.test(gmtDate) && /^\d{2}:\d{2}:\d{2}$/.test(gmtTime)) {
329
+ const utc = `${gmtDate.slice(0, 4)}-${gmtDate.slice(4, 6)}-${gmtDate.slice(6, 8)}T${gmtTime}`;
330
+ const shown = offset ? DateTime.fromISO(utc, { zone: 'utc' }).setZone(FixedOffsetZone.instance(offset)) : null;
331
+ return { time: shown?.isValid && shown.toFormat("yyyy-MM-dd'T'HH:mm':00'") === local
332
+ ? shown.toISO({ suppressMilliseconds: true }) : `${utc}+00:00` };
333
+ }
334
+ if (local) {
335
+ const stated = cleanScalar(activity.gmtOffset);
336
+ return /^[+-]\d{2}:\d{2}$/.test(stated) ? { time: `${local}${stated}` } : { local_time: local };
322
337
  }
323
338
  const day = dateMatch && !localTime ? calendarDay(Number(dateMatch[3]), Number(dateMatch[1]), Number(dateMatch[2])) : null;
324
339
  if (day)
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Universal Parcel Scraper",
5
- "version": "0.652.1"
5
+ "version": "0.652.2"
6
6
  },
7
7
  "paths": {
8
8
  "/health": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "universal-parcel-scraper",
3
- "version": "0.652.1-main.3103",
3
+ "version": "0.652.2-main.3105",
4
4
  "description": "Self-hosted parcel tracking: carrier detection, dedicated scrapers and optional universal providers.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",