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 🙏

© 2024 – Pkg Stats / Ryan Hefner

nifty-tou

v1.2.0

Published

A delightful little data model and utilities for working with time-of-use tariff policies.

Downloads

71

Readme

Nifty ToU (Time of Use)

Nifty ToU is a delightful little JavaScript library for working with time-of-use based tariffs. It aims to be easy to use, reliable, and without any external dependencies.

A central class in Nifty ToU is TemporalRangesTariff, that defines a set of tariff values with a set of time-based constraints. The implication is that the tariff values apply only when all the time-based constraints are valid.

The time-based constraints are encoded as IntRange objects, that are integer ranges with minimum and maximum values that define the bounds of the constraint. The supported time-based constraints are:

| Constraint | Example | Bounds | | :---------------------- | :-------------- | :---------------------------------------------- | | month range | January - March | 1 - 12 (January - December, inclusive) | | day of month range | 1 - 31 | 1 - 31 (inclusive) | | day of week range | Monday - Friday | 1 - 7 (Monday - Sunday, inclusive) | | minute of day range | 00:00 - 08:30 | 0 - 1440 (inclusive minimum, exclusive maximum) |

Here are some example uses of the TemporalRangesTariff class:

// a tariff that applies in the morning of any day of the year
const tt = new TemporalRangesTariff(
	TemporalRangesTariff.ALL_MONTHS,
	TemporalRangesTariff.ALL_DAYS_OF_MONTH,
	TemporalRangesTariff.ALL_DAYS_OF_WEEK,
	new IntRange(0, 720), // midnight - noon
	[
		new TariffRate("Morning Fixed", 1.25),
		new TariffRate("Morning Variable", 0.1),
	]
);

tt.appliesAt(new Date("2024-01-05T01:00")); // true, in the morning
tt.appliesAt(new Date("2024-01-05T13:00")); // false, in the afternoon

Tariff schedules

The TemporalRangesTariffSchedule class defines a schedule, or collection of date-based tariff rules that allows you resolve a set of tariff rates for a given date. For example, imagine we add another tariff to the previous example, and then resolve the rates for a date:

// a tariff that applies after noon of any day of the year
const tt2 = new TemporalRangesTariff(
	TemporalRangesTariff.ALL_MONTHS,
	TemporalRangesTariff.ALL_DAYS_OF_MONTH,
	TemporalRangesTariff.ALL_DAYS_OF_WEEK,
	new IntRange(720, 1440), // noon - midnight
	[new TariffRate("Afternoon Fixed", 2.34)]
);

// create a scheule with our two tariff rules
const schedule = new TemporalRangesTariffSchedule([tt, tt2]);

// resolve the rates that apply on a morning date (8 AM)
const rates = schedule.resolve(new Date("2024-01-05T08:00"));

// rates like:
{
  "Morning Fixed":    {amount: 1.25},
  "Morning Variable": {amount: 0.10}
}

Multiple rules matching

By default, a schedule will resolve the rates for the first available tariff matching a given date. You can turn on multiple match mode by passing an additional true argument to the constructor. For example, imagine we add another tariff to the previous examples, and then resolve the rates for a date:

// a tariff that applies after noon of any day of the year
const tt3 = new TemporalRangesTariff(
	TemporalRangesTariff.ALL_MONTHS,
	TemporalRangesTariff.ALL_DAYS_OF_MONTH,
	TemporalRangesTariff.ALL_DAYS_OF_WEEK,
	TemporalRangesTariff.ALL_MINUTES_OF_DAY,
	[new TariffRate("Any Time", 3.45)]
);

// create a scheule with our three tariff rules, allowing mutiple matches
const schedule = new TemporalRangesTariffSchedule([tt, tt2, tt3], true);

// resolve the rates that apply on a morning date (8 AM)
const rates = schedule.resolve(new Date("2024-01-05T08:00"));

// rates like:
{
  "Morning Fixed":    {amount: 1.25},
  "Morning Variable": {amount: 0.10},
  "All Time":         {amount: 3.45}
}

Year-based tariff schedules

If you would like to model a tariff schedule with rules that change over the time, the YearTemporalRangesTariffSchedule class extends the TemporalRangesTariffSchedule with support for year-based rules. For example, imagine a tariff schedule like this:

| Year | Months | Days | Weekdays | Hours | Name | Rate | | :--- | :----- | :--- | :------- | :---- | :--- | :--- | | 2023 | * | * | * | 0-12 | AM | 1.23 | | 2023 | * | * | * | 12-24 | PM | 2.34 | | 2022 | * | * | * | 0-12 | AM | 1.12 | | 2022 | * | * | * | 12-24 | PM | 2.23 | | 2000 | * | * | * | 0-12 | AM | 0.12 | | 2000 | * | * | * | 12-24 | PM | 0.23 |

You can model this schedule like this:

// define the schedule rules with year constraints
const rules = [
	YearTemporalRangesTariff.parseYears(
		"en-US",
		"2023",
		"Jan-Dec",
		"1-31",
		"Mon-Sun",
		"0-12",
		[new TariffRate("AM", 1.23)]
	),
	YearTemporalRangesTariff.parseYears(
		"en-US",
		"2023",
		"Jan-Dec",
		"1-31",
		"Mon-Sun",
		"12-24",
		[new TariffRate("AM", 2.34)]
	),
	YearTemporalRangesTariff.parseYears(
		"en-US",
		"2022",
		"Jan-Dec",
		"1-31",
		"Mon-Sun",
		"0-12",
		[new TariffRate("AM", 1.12)]
	),
	YearTemporalRangesTariff.parseYears(
		"en-US",
		"2022",
		"Jan-Dec",
		"1-31",
		"Mon-Sun",
		"12-24",
		[new TariffRate("AM", 2.23)]
	),
	YearTemporalRangesTariff.parseYears(
		"en-US",
		"2000",
		"Jan-Dec",
		"1-31",
		"Mon-Sun",
		"0-12",
		[new TariffRate("AM", 0.12)]
	),
	YearTemporalRangesTariff.parseYears(
		"en-US",
		"2000",
		"Jan-Dec",
		"1-31",
		"Mon-Sun",
		"12-24",
		[new TariffRate("AM", 0.23)]
	),
];

// define the schedule, with the `yearExtend` option set
const s = new YearTemporalRangesTariffSchedule(rules, {
	yearExtend: true, // allow "gap fill" year matching
});

// exact match rules
s.resolve(new Date("2023-01-01T08:00")) === { AM: 1.23 };
s.resolve(new Date("2022-01-01T08:00")) === { AM: 1.12 };
s.resolve(new Date("2000-01-01T08:00")) === { AM: 0.12 };

// gap-fill match a future date, based on previously avaialble year rule
s.resolve(new Date("2050-01-01T08:00")) === { AM: 1.23 }; // 2023 rule

// gap-fill match inbetween year rules, based on previously avaialble year
s.resolve(new Date("2010-01-01T08:00")) === { AM: 1.12 }; // 2000 rule

Integer amounts

The TariffRate class can be constructed with an exponent property to avoid floating-point values if desired. For example:

// these floating point rates:
new TariffRate("Morning Fixed", 1.25);
new TariffRate("Morning Variable", 0.1);

// could be expressed in integer form:
new TariffRate("Morning Fixed", 125, -2);
new TariffRate("Morning Variable", 1, -1);

Chronological tariffs

The ChronoTariff class can be used to model a time-based "fixed" tariff, such as a daily or monthly charge. For example:

// construct a chronological tariff @ 10/day
const tariff = new ChronoTariff(ChronoTariffUnit.DAYS, 10);

// calculate the tariff cost over a 7 day time range
const cost =
	tariff.rate *
	tariff.quantity(
		new Date("2024-01-01T00:00:00Z"),
		new Date("2024-01-08T00:00:00Z"),
		true
	);
cost === 70; // 7 days @ 10/day

Language support

Nifty ToU supports parsing and formatting text-based range values, in different languages. For example the following produce the same range constraints and rate values (only the rate names remain language specific):

// US English
const tt = TemporalRangesTariff.parse(
	"en-US",
	"Jan - Dec",
	"1 - 31",
	"Mon - Fri",
	"0 - 24",
	[TariffRate.parse("en-US", "Morning Fixed", "1.23")]
);
TemporalRangesTariff.format(
	"en-US",
	ChronoField.MONTH_OF_YEAR,
	new IntRange(1, 3)
) === "Jan - Mar";

// German
const tt = TemporalRangesTariff.parse(
	"de",
	"Januar - Dezember",
	"1 - 31",
	"Montag - Freitag",
	"00:00 - 24:00",
	[TariffRate.parse("de", "Morgen behoben", "1,23")]
);
TemporalRangesTariff.format(
	"de",
	ChronoField.MONTH_OF_YEAR,
	new IntRange(1, 3)
) === "Jan - Mär";

// Japanese
const tt = TemporalRangesTariff.parse(
	"ja-JP",
	"1月~12月",
	"1~31",
	"月曜日~金曜日",
	"0~24",
	[TariffRate.parse("ja-JP", "固定価格(午前中)", "1.23")]
);
TemporalRangesTariff.format(
	"ja-JP",
	ChronoField.MONTH_OF_YEAR,
	new IntRange(1, 3)
) === "1月~3月";

Documentation

The API documentation is published to https://solarnetwork.github.io/nifty-tou/, and is also available in Markdown form in the docs/md directory.

Building from source

To build Nifty ToU yourself, clone or download this repository. You need to have Node 16+ installed. Then:

# initialize dependencies
npm ci

# build
npm run build

Running the build script will execute the TypeScript compiler and generate JavaScript files in lib/ directory.

Building API documentation

To build the API documentation, you must first build the package and then run npm run apidocs. For example:

npm run apidocs

Unit tests

You can run the unit tests with npm test. For example:

npm test

...
  ✔ ChronoFieldParserTests › ChronoFieldParser:parseRange:month:fr-FR
  ✔ ChronoFieldParserTests › ChronoFieldParser:parseRange:month:nums:fr-FR
  ✔ ChronoFieldParserTests › ChronoFieldParser:parseRange:week:en-US
  ✔ ChronoFieldParserTests › ChronoFieldParser:parseRange:week:nums:en-US
  ✔ ChronoFieldParserTests › ChronoFieldParser:parseRange:week:fr-FR
  ✔ ChronoFieldParserTests › ChronoFieldParser:parseRange:week:nums:fr-FR
  ─

  107 tests passed
-------------------------|---------|----------|---------|---------|-------------------
File                     | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------------------|---------|----------|---------|---------|-------------------
All files                |     100 |      100 |     100 |     100 |
 ChronoFieldParser.ts    |     100 |      100 |     100 |     100 |
 IntRange.ts             |     100 |      100 |     100 |     100 |
 NumberParser.ts         |     100 |      100 |     100 |     100 |
 TariffRate.ts           |     100 |      100 |     100 |     100 |
 TemporalRangesTariff.ts |     100 |      100 |     100 |     100 |
 utils.ts                |     100 |      100 |     100 |     100 |
-------------------------|---------|----------|---------|---------|-------------------

Test coverage

codecov

Having a well-tested and reliable library is a core goal of this project. Unit tests are executed automatically after every push into the main branch of this repository and their associated code coverage is uploaded to Codecov.

codecov