npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gstypecode/time-fields

v0.1.0

Published

Time field normalization utilities shared across data pipelines.

Readme

@gstypecode/time-fields

When "01:00 on September 21" actually means "01:00 on September 22"

The Problem: Operational Dates vs. Calendar Dates

In industries that operate through the night — restaurants, hospitals, call centers, security, logistics — workers think of their shift as one continuous block. A bar that opens at 20:00 and closes at 03:00 considers the entire shift as "September 21st," even though 03:00 technically falls on September 22nd.

When these shifts were tracked on paper forms, staff naturally wrote the operational date ("September 21") alongside clock times like "01:00" or "02:30." Everyone understood that "01:00 on September 21" meant the early morning after that business day — not 1 AM at the start of it.

Then the paper records got digitized. The dates and times were entered exactly as written. And now your database has timestamps that are off by a day:

Stored data:
  date:  "2025-09-21"
  start: "01:00"       ← meant Sept 22 at 01:00
  end:   "06:00"       ← meant Sept 22 at 06:00

Naively combined:
  start = 2025-09-21T01:00:00   ← WRONG. Off by one day.

This isn't a bug — it's a legacy of how humans naturally track overnight work. But if you don't correct it, every downstream calculation — duration, overlap detection, payroll, analytics — silently produces wrong results.

This library detects and corrects these date shifts, converting operational-date records into accurate calendar timestamps.


How It Works

The core idea: if a start time falls within a "late-night window" (by default, before 03:00), the library recognizes it as belonging to the next calendar day and shifts it forward by 24 hours.

import { computeTimeFields } from "@gstypecode/time-fields";

const result = computeTimeFields({
  date: new Date("2025-09-21T00:00:00+09:00"),  // operational date from the form
  start: "01:00",       // clock time as written
  end: "06:00",
  trueTimeLength: "05:00",
});

result.start_timestamp;  // => 2025-09-22T01:00:00+09:00  ✅ Corrected
result.end_timestamp;    // => 2025-09-22T06:00:00+09:00  ✅ Corrected
result.duration_hours;   // => 5
result.start_shift_flag; // => true (date correction was applied)

Install

npm install @gstypecode/time-fields

Understanding the Options

Each option maps directly to a real-world concept. Here's what they control and why you might change them.

The Business-Day Cutoff: shiftThresholdMinutes

What it means: "How late into the night do we still consider it 'yesterday'?"

The default is 180 (= 03:00). Any start time before 03:00 is treated as the next calendar day.

| Scenario | Setting | |---|---| | Bar closing at 03:00 — shifts never start after 03:00 | 180 (default) | | 24-hour hospital — handoff at 06:00 | 360 | | Office with occasional overtime past midnight | 60 |

const factory = createTimeFields({
  shiftThresholdMinutes: 360,  // anything before 06:00 = next day
});

Cutoff Boundary: shiftComparisonOperator

What it means: "Does the cutoff time itself count as 'yesterday' or 'today'?"

  • "<" (default): start times strictly less than the threshold are shifted. A start of exactly 03:00 is NOT shifted.
  • "<=": start times at or below the threshold are shifted. A start of exactly 03:00 IS shifted.

Day Correction Amount: dayOffsetMinutes

What it means: "How many minutes to add when correcting the date."

Default is 1440 (= 24 hours). You almost never need to change this, unless your operational calendar uses non-standard day lengths.

End-Time Day Crossing: endCrossesDayComparison

What it means: "How to detect when the end time has also crossed into the next calendar day."

When the end time is numerically smaller than the start time (e.g., start: "22:00", end: "02:00"), the end time must also be shifted forward. This option controls the comparison operator for that detection, separately for shifted and non-shifted starts:

endCrossesDayComparison: {
  shifted: "<=",    // when start was date-corrected
  notShifted: "<",  // when start was NOT date-corrected
}

Time Format: timeFormatRegex

What it means: "What format are the time strings in?"

Default matches HH:MM (e.g., "01:00", "23:59"). Override if your data uses a different format.

Confidence Values: confidence

What it means: "Numeric labels for how certain the shift detection was."

  • confidence.determined (default: 1): the library had enough data to make a definitive decision.
  • confidence.default (default: 0): no start time was provided, so no determination was possible.

These values appear in start_shift_confidence in the result. Useful for flagging records that need manual review.


API

computeTimeFields(payload)

The main function. Takes an operational-date record and returns corrected timestamps.

Payload:

| Field | Type | Description | |---|---|---| | date | Date | The operational date (the date written on the form) | | start | string? | Start time in HH:MM | | end | string? | End time in HH:MM | | trueTimeLength | string? | Actual working duration in HH:MM |

Result:

| Field | Type | Description | |---|---|---| | start_timestamp | Date \| null | Corrected start timestamp | | end_timestamp | Date \| null | Corrected end timestamp | | start_shift_flag | boolean \| null | Whether date correction was applied | | start_shift_confidence | number | Confidence of the shift detection (1 = determined, 0 = unknown) | | duration_hours | number \| null | Working duration in decimal hours | | raw.start_minutes | number \| null | Parsed start time in minutes from midnight | | raw.end_minutes | number \| null | Parsed end time in minutes from midnight | | raw.duration_minutes | number \| null | Parsed duration in minutes |

createTimeFields(options?)

Factory API. Creates a configured instance for processing many records with the same rules.

import { createTimeFields } from "@gstypecode/time-fields";

const tf = createTimeFields({
  shiftThresholdMinutes: 360,
  shiftComparisonOperator: "<=",
});

for (const record of records) {
  const result = tf.computeTimeFields(record);
}

Utility Functions

| Function | Description | |---|---| | parseTimeToMinutes(str) | Parse HH:MM string to minutes from midnight | | parseDurationToMinutes(str) | Same as above (semantic alias for duration strings) | | computeShift(minutes) | Test whether a time in minutes would be date-shifted | | addMinutes(date, min) | Add minutes to a Date (non-mutating) | | constants.DEFAULT_OPTIONS | Default configuration (frozen object) |


Use Cases

  • Hospitality / nightlife: Shifts spanning midnight recorded under the opening date
  • Healthcare: Night-shift nursing logs written under the admission date
  • Call centers: Overnight support tickets timestamped with the business date
  • Security / logistics: Guard rotations and delivery windows crossing midnight
  • ETL pipelines: Normalizing legacy paper-form data for analytics

License

MIT