0.0.3 • Published 2 years ago

temporalo v0.0.3

Weekly downloads
-
License
MIT
Repository
-
Last release
2 years ago

Temporalo

Temporalo - Another lightweight datetime processing package

The name comes from Esperanto, meaning "temporal" in English (exactly the incoming new ECMAScript Date/Time API).

Features:

  • For browsers & Node.js & Deno
  • ES module
  • Lightweight
  • Out of time zones, out of mind
  • Proudly made in PRC

TODO:

  • More APIs
  • Tests
  • TypeScript
  • Error handling
  • Support for ES2021 and lower
  • IIFE and CMD packages?

Installation

npm install temporalo
yarn add temporalo
pnpm add temporalo
<script src="https://cdn.jsdelivr.net/npm/temporalo/dist/temporalo.umd.js"></script>
<script type="importmap">
{
  "imports": {
    "temporalo": "https://cdn.jsdelivr.net/npm/temporalo/dist/temporalo.modern.js"
  }
}
</script>
const Temporalo = require('temporalo/dist/temporalo.cjs')
import Temporalo from 'temporalo/dist/temporalo.modern.js'

The Temporalo instance

There are three ways to get a Temporalo instance.

  1. Using the constructor new Temporalo().

    new Temporalo()
    new Temporalo(value)
    new Temporalo(dateString)
    new Temporalo(dateObject)
    new Temporalo(datoObject)

    If the constructor is passed with:

    • a Date instance, it returns a Temporalo instance of the same datetime value.

    • a number or a string representing a datetime, it parses it using new Date() and returns a Temporalo instance of the same datetime value.

    • another Temporalo instance, it returns a clone of that Temporalo instance.

    • no argument, then it returns a Temporalo instance of the current datetime in the local time zone.

  2. Using the static method Temporalo.from().

    This static method accepts the same argument that the constructor does and returns the same result that it returns.

  3. As a returned value of FP style Temporalo instance methods.

    Most Temporalo instance methods return a new Temporalo instance, so it will be perfect for FP-style method-chaining, leaving a more readable code and fewer errors.

The only property of the Temporalo instance is value, which is simply a string in the format of either YYYY-MM-DDTHH:mm:ss.sssZ or ±YYYYYY-MM-DDTHH:mm:ss.sss that complies to ISO 8601. Although reassigning this value is allowed, it is strongly NOT RECOMMENDED to do so, in that there might be unpredictable side effects that compromise the program. As a best practice, always use Temporalo instances generated by the above three ways.

In the following part of this tutorial, we assume d to be a Temporalo instance with a date value of Feb. 22, 2022 and a time value of 22:22:22.222.

const d = new Temporalo('2022-02-22T22:22:22.222Z')

Formatting

So far there are three very basic formmating methods: date() and time() returning the date part and the time part, and format() that accepts the same argument as Intl.DateTimeFormat() constructor and returns the same result as Intl.DateTimeFormat.prototype.format(). More customized formatters are on the way.

console.log([
  d.date(),
  d.time(),
  d.format('en-US', { dateStyle: 'full', timeStyle: 'full', timeZone: 'UTC' })
])
/*
Result:
[
  "2022-02-22",
  "22:22:22",
  "Tuesday, February 22, 2022 at 10:22:22 PM Coordinated Universal Time"
]
*/

Getting datetime info

The get() method returns a plain object that describes datetime info with the following keys:

  • year: An integer representing the year.
  • month: An integer from 1 to 12 representing the month.
  • day: An integer from 1 to 31 representing the day of the month.
  • hours: An integer from 0 to 23 representing the hours.
  • minutes: An integer from 0 to 59 representing the minutes.
  • seconds: An integer from 0 to 59 representing the seconds. Leap seconds are ignored.
  • ms: An integer from 0 to 999 representing the milliseconds.
  • weekday: An integer from 0 to 6 representing the milliseconds, which maps to weekdays from Sunday to Saturday.
console.log(d.get())
/*
Result:
{
  year: 2022,
  month: 2,
  day: 22,
  hours: 22,
  minutes: 22,
  seconds: 22,
  ms: 222,
  weekday: 2
}
*/

Sometimes when only one value of the datetime info is needed, it is natural to pass a unit to the get() method, or even more conveniently, to use the unit methods as a grammar sugar.

Here is a table listing all ways to get one value of the datetime info.

Value to getUsing the get() methodUsing the unit methodReturned Value
yeard.get('year')d.year()2022
monthd.get('month')d.month()2
dayd.get('day')d.day()22
hoursd.get('hours')d.hours()22
minutesd.get('minutes')d.minutes()22
secondsd.get('seconds')d.seconds()22
millisecondsd.get('ms')d.ms()222
weekdayd.get('weekday')d.weekday()2

Setting date Info

Remember in the above chapters when I told you not to play with the value property? What if you really need to change one or more values of a datetime like setting the year or day? Here comes the setter methods to save the day.

All setter methods returns a brand new Temporalo instance based on the current one, with only one or more particular values are altered. Therefore, no value is altered in the process, and side effects are eliminated.

One way to use the set() method is to pass an object as its only argument. The object has a similar structure to the get() method's result, with two differences:

  1. You can send only a subset of the units, and for those not in the object, they stay the same as in the base instance.

  2. weekday makes no difference at all, because it is unable to set the weekday without changing other date values, so you may not need it in the object passed.

console.log([
  d.set({ month: 10, day 10 }),
  d.set('month', 10).set('day', 10),
  d.month(10).day(10)
])
console.log(d)
/*
Result
[
  Temporalo { value: "2022-10-10T22:22:22.222Z" },
  Temporalo { value: "2022-10-10T22:22:22.222Z" },
  Temporalo { value: "2022-10-10T22:22:22.222Z" }
]
Temporalo { value: "2022-02-22T22:22:22.222Z" }
*/

Still, you can just set one value at a time by passing a second argument to the set() method, or use the unit methods. Since weekday does not work here, the getter methods get('weekday') and weekday() have no setter counterparts.

Here is a table listing all ways to set one value of the datetime info.

Value to setUsing set methodUsing unit methodReturned Value
yeard.set('year', 2020)d.year(2020)Temporalo { value: "2020-02-22T22:22:22.222Z" }
monthd.set('month', 10)d.month(10)Temporalo { value: "2022-10-22T22:22:22.222Z" }
dayd.set('day', 1)d.day(1)Temporalo { value: "2022-02-01T22:22:22.222Z" }
hoursd.set('hours', 12)d.hours(12)Temporalo { value: "2022-02-22T12:22:22.222Z" }
minutesd.set('minutes', 15)d.minutes(15)Temporalo { value: "2022-02-22T22:15:22.222Z" }
secondsd.set('seconds', 15)d.seconds(15)Temporalo { value: "2022-02-22T22:22:15.222Z" }
millisecondsd.set('ms', 500)d.ms(500)Temporalo { value: "2022-02-22T22:22:22.500Z" }

Note: It is OK to send out-of-range values to setters as long as they remain integers. When such values are passed, the setters will automatically calculate the result datetime. However, you should be very cautious and check the results, because they might not be what you expect.

console.log([
  d.month(0),
  d.day(-1),
  d.hours(24)
])
/*
Result
[
  Temporalo { value: "2021-12-22T22:22:22.222Z" },
  Temporalo { value: "2022-01-30T22:22:22.222Z" },
  Temporalo { value: "2022-02-23T00:22:22.222Z" }
]
*/

The formatter time() also functions as a getter. When it receives a number, it keeps the date part and starts counting seconds from midnight. When it receives a time string, it combines that time with the date part.

console.log([
  d.time(0),
  d.time(3600),
  d.time('08:00'),
  d.time('11:11:11')
])
/*
Result
[
  Temporalo { value: "2022-02-22T00:00:00.000Z" },
  Temporalo { value: "2022-02-22T01:00:00.000Z" },
  Temporalo { value: "2022-02-22T08:00:00.000Z" },
  Temporalo { value: "2022-02-22T11:11:11.000Z" }
]
*/

Getting date range

Ranges are intervals of datetime with fixed starting points and ending points. They are especially helpful for setting presets in front-end date-pickers among many other uses.

As with the getter and setter methods, there are two kinds of methods to get a range. One is to use the range() method, and the other is, of course, the grammar sugar unit methods. Since the bare unit methods are used to be setters, an underscore symbol (_) is prefixed to the unit.

All range methods return a binary array of Temporalo instances. The former represents the first instant of start date with a time of T00:00:00.000Z, and the latter the last instant with a time of T23:59:59.999Z.

Ranges only apply to date, so units under day like hours and minutes are not allowed. Another two units, however, are introduced: week and quarter. A week is defined as a 7-day interval from Monday to Sunday according to ISO 8601, and a quarter is a following three-month interval beginning at January, April, July, or October.

Sometimes it is useful to get part of a range that ends on a particular day, like the part of the current year that has passed, which is where the untilNow option comes in handy. In other circumstances, when a range that represents the following year, not the next year, is needed, the relative option will help. In the relative mode, the untilNow option has no effect, and day units like week, month, quarter, and year are no longer intervals with fixed positions, but with a fixed length, representing 7, 30, 90, 365 days respectively.

Here is a large cheat sheet of range examples for those who believe the above paragraphs TL;DR. (To make the table simple, only the date part of the results is given, just keep in mind they are actually arrays of two Temporalo instances.)

Range to getUsing the range() methodUsing the unit methodResult
current full dayd.range('day')d._day()2022-02-22 ➡️ 2022-02-22
current full weekd.range('week')d._week()2022-02-21 ➡️ 2022-02-27
current full monthd.range('month')d._month()2022-02-01 ➡️ 2022-02-28
current full quarterd.range('quarter')d._quarter()2022-01-01 ➡️ 2022-03-31
current full yeard.range('year')d._year()2022-01-01 ➡️ 2022-12-31
current week until nowd.range('week', 0, { untilNow: true })d._week(0, { untilNow: true })2022-02-21 ➡️ 2022-02-22
current month until nowd.range('month', 0, { untilNow: true })d._month(0, { untilNow: true })2022-02-01 ➡️ 2022-02-22
current quarter until nowd.range('quarter', 0, { untilNow: true })d._quarter(0, { untilNow: true })2022-01-01 ➡️ 2022-02-22
current year until nowd.range('year', 0, { untilNow: true })d._year(0, { untilNow: true })2022-01-01 ➡️ 2022-02-22
last full dayd.range('day', -1)d._day(-1)2022-02-21 ➡️ 2022-02-21
last full weekd.range('week', -1)d._week(-1)2022-02-14 ➡️ 2022-02-20
last full monthd.range('month', -1)d._month(-1)2022-01-01 ➡️ 2022-01-31
last full quarterd.range('quarter', -1)d._quarter(-1)2021-10-01 ➡️ 2021-12-31
last full yeard.range('year', -1)d._year(-1)2021-01-01 ➡️ 2021-12-31
next full dayd.range('day', 1)d._day(1)2022-02-23 ➡️ 2022-02-23
next full weekd.range('week', 1)d._week(1)2022-02-28 ➡️ 2022-03-06
next full monthd.range('month', 1)d._month(1)2022-03-01 ➡️ 2022-03-31
next full quarterd.range('quarter', 1)d._quarter(1)2022-04-01 ➡️ 2022-06-30
next full yeard.range('year', 1)d._year(1)2021-01-01 ➡️ 2021-12-31
last 7 daysd.range('week', -1, { relative: true })d._week(-1, { relative: true })2022-02-14 ➡️ 2022-02-20
last 30 daysd.range('month', -1, { relative: true })d._month(-1, { relative: true })2022-01-01 ➡️ 2022-01-31
last 90 daysd.range('quarter', -1, { relative: true })d._quarter(-1, { relative: true })2021-10-01 ➡️ 2021-12-31
last 365 daysd.range('year', -1, { relative: true })d._year(-1, { relative: true })2021-01-01 ➡️ 2021-12-31
next 7 daysd.range('week', 1, { relative: true })d._week(1, { relative: true })2022-02-28 ➡️ 2022-03-06
next 30 daysd.range('month', 1, { relative: true })d._month(1, { relative: true })2022-03-01 ➡️ 2022-03-31
next 90 daysd.range('quarter', 1, { relative: true })d._quarter(1, { relative: true })2022-04-01 ➡️ 2022-06-30
next 365 daysd.range('year', 1, { relative: true })d._year(1, { relative: true })2021-01-01 ➡️ 2021-12-31

Datetime calculation

Datetime calculation methods have a lot in common with setters. They both return a new Temporalo instance based on the current one, and the argument structures are almost the same. While setters explicitly set the values of datetime info, these methods calculate the datetime with interval length, providing more flexibility.

There are two calculation methods: before() and after(). To use them, either pass one object or a unit with a value to it, and you will get the result. Their functions are easily seen considering the names, but the roles will reverse when negative values are passed.

Unit methods as grammar sugar also exist. They function the same as after() and are prefixed with a dollar sign ($). Allowed units are year, month, day, hours, minutes, seconds, and ms.

Instead of a large cheatsheet, a few examples here will suffice.

console.log([
  d.before('day', 2),
  d.before('day', -2),
  d.after('day', 2),
  d.after('day', -2),
  d.$day(2),
  d.$day(-2)
])
/*
Result:
[
  Temporalo { value: "2022-02-20T22:22:22.222Z" },
  Temporalo { value: "2022-02-24T22:22:22.222Z" },
  Temporalo { value: "2022-02-24T22:22:22.222Z" },
  Temporalo { value: "2022-02-20T22:22:22.222Z" },
  Temporalo { value: "2022-02-24T22:22:22.222Z" },
  Temporalo { value: "2022-02-20T22:22:22.222Z" }
]
*/

Datetime interval calculation

Datetime interval calculation methods are not finished yet. In the plan, the API will be like this:

d.diff('day', d.$day(1)) // 1
d.diff('hours', d.$day(1)) // 24
d.diff('day', d.$hours(12)) // 0.5
d.diff('full', d.$hours(36)) // { day: 1, hours: 12 }

In the meanwhile, I am running out of grammar sugar symbols. Maybe double dollar sign ($$)? Any ideas, guys?

Appendix: List of methods, units, and grammar sugars

unitget(unit)unit()set(unit, value)unit(value)after(unit, value)before(unit, -value)$unit(value)range(unit, offset)_unit(offset)range(unit, offset,{ untilNow: true })_unit(offset, { untilNow: true })range(unit, offset,{ relative: true })_unit(offset, { relative: true })
year✔︎✔︎✔︎✔︎✔︎✔︎
month✔︎✔︎✔︎✔︎✔︎✔︎
day✔︎✔︎✔︎✔︎✔︎
hours✔︎✔︎✔︎
minutes✔︎✔︎✔︎
seconds✔︎✔︎✔︎
ms✔︎✔︎✔︎
weekday✔︎
week✔︎✔︎✔︎✔︎
quarter✔︎✔︎✔︎
typemethodinputoutput
constructornew Temporalo()undefinedstringnumberDate{}Temporalo{}Temporalo{}
constructorTemporalo.from()undefinedstringnumberDate{}Temporalo{}Temporalo{}
formatterformat()undefined, undefinedstring, undefinedstring, Object{}string
formatterdate()undefinedstring
formattertime()undefinedstring
getterget()undefinedObject{}
getterunit()undefinednumber
setterset()Object{}Temporalo{}
setterset()'string', numberTemporalo{}
setterunit()numberTemporalo{}
settertime()numberstringTemporalo{}
range getterrange()string, undefined, undefinedstring, number, undefinedstring, number, Object{}Temporalo{}, Temporalo{}
range getter_unit()undefined, undefinednumber, undefinednumber, Object{}Temporalo{}, Temporalo{}
calculatorafter()string, numberTemporalo{}
calculatorbefore()string, numberTemporalo{}
calculator\$unit()numberTemporalo{}
misctoDate()undefinedDate{}
misctoJSON()undefinedstring