Skip to content

Commit

Permalink
Provide time.toInstant method
Browse files Browse the repository at this point in the history
Resolves #412

Signed-off-by: Jacob Laursen <[email protected]>
  • Loading branch information
jlaur committed Jan 6, 2025
1 parent d5c8fd8 commit d02bb08
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 22 deletions.
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -971,7 +971,7 @@ The following rules are used during the conversion:
| `null` or `undefined` | `time.ZonedDateTime.now()` | `time.toZDT();` |
| `time.ZonedDateTime` | passed through unmodified | |
| `java.time.ZonedDateTime` | converted to the `time.ZonedDateTime` equivalent | |
| JavaScript native `Date` | converted to the equivalent `time.ZonedDateTime` using `SYSTEM` as the timezone | |
| JavaScript native `Date` | converted to the `time.ZonedDateTime` equivalent using `SYSTEM` as the timezone | |
| `number`, `bingint`, `java.lang.Number`, `DecimalType` | rounded to the nearest integer and added to `now` as milliseconds | `time.toZDT(1000);` |
| [`Quantity`](#quantity) or `QuantityType` | if the unit is time-compatible, added to `now` | `time.toZDT(item.getItem('MyTimeItem').rawState);`, `time.toZDT(Quantity('10 min'));` |
| `items.Item` or `org.openhab.core.types.Item` | if the state is supported (see the `Type` rules in this table, e.g. `DecimalType`), the state is converted | `time.toZDT(items.getItem('MyItem'));` |
Expand Down Expand Up @@ -1089,6 +1089,24 @@ var timestamp = time.ZonedDateTime.now().plusMinutes(5);
console.log(timestamp.getMillisFromNow());
```
#### `time.toInstant()`
The following rules are used during the conversion:
| Argument Type | Rule | Examples |
|------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|----------------------------------------------|
| `null` or `undefined` | `time.Instant.now()` | `time.toInstant();` |
| `time.Instant` | passed through unmodified | |
| `java.time.Instant` | converted to the `time.Instant` equivalent | |
| `java.time.ZonedDateTime` | converted to the `time.Instant` equivalent | |
| JavaScript native `Date` | converted to the `time.Instant` equivalent | |
| `items.Item` or `org.openhab.core.types.Item` | if the state is supported (see the `Type` rules in this table, e.g. `DecimalType`), the state is converted | `time.toInstant(items.getItem('MyItem'));` |
| `String`, `java.lang.String`, `StringType` | parsed ISO Instant | `time.toInstant('2019-10-12T07:20:50.52Z');` |
| [ISO8601 Date/Time](https://en.wikipedia.org/wiki/ISO_8601) String | | |
| RFC String (output from a Java `Instant.toString()`) | | |
When a type or string that cannot be handled is encountered, an error is thrown.
### Quantity
The `Quantity` class greatly simplifies Quantity handling by providing unit conversion, comparisons and mathematical operations.
Expand Down
138 changes: 120 additions & 18 deletions src/time.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ const time = require('@js-joda/core');

const log = require('./log')('time');
const osgi = require('./osgi');
const { _isItem, _isZonedDateTime, _isDuration, _isQuantity } = require('./helpers');
const { _isItem, _isZonedDateTime, _isInstant, _isDuration, _isQuantity } = require('./helpers');

const javaZDT = Java.type('java.time.ZonedDateTime');
const javaInstant = Java.type('java.time.Instant');
const javaDuration = Java.type('java.time.Duration');
const javaString = Java.type('java.lang.String');
const javaNumber = Java.type('java.lang.Number');
Expand All @@ -41,28 +42,30 @@ time.ZonedDateTime.prototype.parse = function (text, formatter = rfcFormatter) {
};

/**
* Adds millis to the passed in ZDT as milliseconds. The millis is rounded first.
* Adds millis to the passed in Temporal as milliseconds. The millis are rounded first.
* If millis is negative they will be subtracted.
* @private
* @param {time.Temporal} temporal temporal to add milleseconds to
* @param {number|bigint} millis number of milliseconds to add
*/
function _addMillisToNow (millis) {
return time.ZonedDateTime.now().plus(Math.round(millis), time.ChronoUnit.MILLIS);
function _addMillis (temporal, millis) {
return temporal.plus(Math.round(millis), time.ChronoUnit.MILLIS);
}

/**
* Adds the passed in QuantityType<Time> to now.
* Adds the passed in QuantityType<Time> to the passed Temporal.
* @private
* @param {time.Temporal} temporal temporal to add seconds to
* @param {QuantityType} quantityType an Item's QuantityType
* @returns now plus the time length in the quantityType
* @throws error when the units for the quantity type are not one of the Time units
*/
function _addQuantityType (quantityType) {
function _addQuantityType (temporal, quantityType) {
const secs = quantityType.toUnit('s');
if (secs) {
return time.ZonedDateTime.now().plusSeconds(secs.doubleValue());
return temporal.plus(secs.doubleValue(), time.ChronoUnit.SECONDS);
} else {
throw Error('Only Time units are supported to convert QuantityTypes to a ZonedDateTime: ' + quantityType.toString());
throw Error('Only Time units are supported to add QuantityTypes to a Temporal: ' + quantityType.toString());
}
}

Expand Down Expand Up @@ -180,20 +183,41 @@ function _parseString (str) {
* @returns {time.ZonedDateTime}
* @throws error if the Item's state is not supported or the Item itself is not supported
*/
function _convertItemRawState (rawState) {
function _convertItemRawStateToZonedDateTime (rawState) {
if (rawState instanceof DecimalType) { // Number type Items
return _addMillisToNow(rawState.floatValue());
return _addMillis(time.ZonedDateTime.now(), rawState.floatValue());
} else if (rawState instanceof StringType) { // String type Items
return _parseString(rawState.toString());
} else if (rawState instanceof DateTimeType) { // DateTime Items
return javaInstantToJsInstant(rawState.getInstant()).atZone(time.ZoneId.systemDefault());
} else if (rawState instanceof QuantityType) { // Number:Time type Items
return _addQuantityType(rawState);
return _addQuantityType(time.ZonedDateTime.now(), rawState);
} else {
throw Error(rawState.toString() + ' is not supported for conversion to time.ZonedDateTime');
}
}

/**
* Converts the state of the passed in Item to a time.Instant
* @private
* @param {HostState} rawState
* @returns {time.Instant}
* @throws error if the Item's state is not supported or the Item itself is not supported
*/
function _convertItemRawStateToInstant (rawState) {
if (rawState instanceof DecimalType) { // Number type Items
return _addMillis(time.Instant.now(), rawState.floatValue());
} else if (rawState instanceof StringType) { // String type Items
return time.Instant.parse(rawState.toString());
} else if (rawState instanceof DateTimeType) { // DateTime Items
return javaInstantToJsInstant(rawState.getInstant());
} else if (rawState instanceof QuantityType) { // Number:Time type Items
return _addQuantityType(time.Instant.now(), rawState);
} else {
throw Error(rawState.toString() + ' is not supported for conversion to time.Instant');
}
}

/**
* Convert Java Instant to JS-Joda Instant.
*
Expand Down Expand Up @@ -255,7 +279,7 @@ function toZDT (when) {
}
// Convert Java ZonedDateTime
if (when instanceof javaZDT) {
log.debug('toZTD: Converting Java ZonedDateTime ' + when.toString());
log.debug('toZDT: Converting Java ZonedDateTime ' + when.toString());
return javaZDTToJsZDT(when);
}

Expand All @@ -282,10 +306,10 @@ function toZDT (when) {
// Add JavaScript's number or JavaScript BigInt or Java Number or Java DecimalType as milliseconds to now
if (typeof when === 'number' || typeof when === 'bigint') {
log.debug('toZDT: Adding milliseconds ' + when + ' to now');
return _addMillisToNow(when);
return _addMillis(time.ZonedDateTime.now(), when);
} else if (when instanceof javaNumber || when instanceof DecimalType) {
log.debug('toZDT: Adding Java number or DecimalType ' + when.floatValue() + ' to now');
return _addMillisToNow(when.floatValue());
return _addMillis(time.ZonedDateTime.now(), when.floatValue());
}

// DateTimeType, extract the javaInstant and convert to time.ZDT, use the configured timezone
Expand All @@ -297,10 +321,10 @@ function toZDT (when) {
// Add Quantity or QuantityType<Time> to now
if (_isQuantity(when)) {
log.debug('toZDT: Adding Quantity ' + when + ' to now');
return _addQuantityType(when.rawQtyType);
return _addQuantityType(time.ZonedDateTime.now(), when.rawQtyType);
} else if (when instanceof QuantityType) {
log.debug('toZDT: Adding QuantityType ' + when + ' to now');
return _addQuantityType(when);
return _addQuantityType(time.ZonedDateTime.now(), when);
}

// Convert items.Item or raw Item
Expand All @@ -309,10 +333,10 @@ function toZDT (when) {
if (when.isUninitialized) {
throw Error('Item ' + when.name + ' is NULL or UNDEF, cannot convert to a time.ZonedDateTime');
}
return _convertItemRawState(when.rawState);
return _convertItemRawStateToZonedDateTime(when.rawState);
} else if (when instanceof ohItem) {
log.debug('toZDT: Converting raw Item ' + when);
return _convertItemRawState(when.getState());
return _convertItemRawStateToZonedDateTime(when.getState());
}

// Unsupported
Expand All @@ -331,6 +355,83 @@ time.ZonedDateTime.prototype.toToday = function () {
.withDayOfMonth(now.dayOfMonth());
};

/**
* Converts the passed in when to a time.Instant based on the following
* set of rules.
*
* - null, undefined: time.Instant.now()
* - time.Instant: unmodified
* - time.ZonedDateTime: converted to the time.Instant equivalent
* - Java Instant, DateTimeType: converted to time.Instant equivalent
* - JavaScript native {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date Date}: converted to a `time.Instant`
* - Item: converts the state of the Item based on the *Type rules described here
* - String, Java String, StringType: parsed ISO Instant
* - RFC (output from a Java Instant.toString()): parsed to time.ZonedDateTime
* @memberof time
* @param {*} [when] any of the types discussed above
* @returns {time.Instant}
* @throws error if the type, format, or contents of when are not supported
*/
function toInstant (when) {
// If when is not supplied or null, return now
if (when === undefined || when === null) {
log.debug('toInstant: Returning Instant.now()');
return time.Instant.now();
}

// Pass through if already a time.Instant
if (_isInstant(when)) {
log.debug('toInstant: Passing trough ' + when);
return when;
}

// Convert time.ZonedDateTime
if (_isZonedDateTime(when)) {
log.debug('toInstant: Converting time.ZonedDateTime ' + when.toString());
return when.toInstant();
}

// Convert Java Instant
if (when instanceof javaInstant) {
log.debug('toInstant: Converting Java Instant ' + when.toString());
return javaInstantToJsInstant(when);
}

// String or StringType
if (typeof when === 'string' || when instanceof javaString || when instanceof StringType) {
log.debug('toInstant: Parsing string ' + when);
return time.Instant.parse(when.toString());
}

// JavaScript Native Date
if (when instanceof Date) {
log.debug('toInstant: Converting JS native Date ' + when);
const native = time.nativeJs(when);
return time.Instant.from(native);
}

// DateTimeType, extract the javaInstant and convert to time.Instant
if (when instanceof DateTimeType) {
log.debug('toInstant: Converting DateTimeType ' + when);
return javaInstantToJsInstant(when.getInstant());
}

// Convert items.Item or raw Item
if (_isItem(when)) {
log.debug('toInstant: Converting Item ' + when);
if (when.isUninitialized) {
throw Error('Item ' + when.name + ' is NULL or UNDEF, cannot convert to a time.Instant');
}
return _convertItemRawStateToInstant(when.rawState);
} else if (when instanceof ohItem) {
log.debug('toInstant: Converting raw Item ' + when);
return _convertItemRawStateToInstant(when.getState());
}

// Unsupported
throw Error('"' + when + '" is an unsupported type for conversion to time.Instant');
}

/**
* Tests whether `this` time.ZonedDateTime is before the passed in timestamp.
* However, the function only compares the time portion of both, ignoring the date portion.
Expand Down Expand Up @@ -497,6 +598,7 @@ module.exports = {
javaInstantToJsInstant,
javaZDTToJsZDT,
toZDT,
toInstant,
_parseString,
_parseISO8601
};
3 changes: 2 additions & 1 deletion test/jest.setup.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
const { ModuleBuilder, Configuration, QuantityType, JavaScriptExecution, JavaTransformation, JavaNotificationAction } = require('./openhab.mock');
const { Class, BigDecimal, ArrayList, HashSet, Hashtable, UUID, FrameworkUtil, LoggerFactory, ZonedDateTime } = require('./java.mock');
const { Class, BigDecimal, ArrayList, HashSet, Hashtable, UUID, FrameworkUtil, LoggerFactory, Instant, ZonedDateTime } = require('./java.mock');

const TYPES = {
'java.lang.Class': Class,
'java.math.BigDecimal': BigDecimal,
'java.time.Instant': Instant,
'java.time.ZonedDateTime': ZonedDateTime,
'java.util.ArrayList': ArrayList,
'java.util.HashSet': HashSet,
Expand Down
22 changes: 21 additions & 1 deletion test/time.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,27 @@ describe('time.js', () => {

it('delegates to parseString', () => {
const when = 'when';
expect(() => time.toZDT(when)).toThrowError('Failed to parse string when: DateTimeParseException: Text cannot be parsed to a Duration: when, at index: 0');
expect(() => time.toZDT(when)).toThrow('Failed to parse string when: DateTimeParseException: Text cannot be parsed to a Duration: when, at index: 0');
});

// TODO: Add remaining possible cases for when
});

describe('toInstant', () => {
it('passes through if when is a time.Instant', () => {
const instant = time.Instant.now();
expect(time.toInstant(instant)).toBe(instant);
});

it('converts if when is a time.ZonedDateTime', () => {
const zdt = time.ZonedDateTime.now();
expect(time.toInstant(zdt)).toStrictEqual(zdt.toInstant());
});

it('converts if when is a string', () => {
const instant = time.Instant.now();
const string = instant.toString();
expect(time.toInstant(string)).toStrictEqual(instant);
});

// TODO: Add remaining possible cases for when
Expand Down
19 changes: 19 additions & 0 deletions types/time.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ declare const _exports: {
javaInstantToJsInstant: typeof javaInstantToJsInstant;
javaZDTToJsZDT: typeof javaZDTToJsZDT;
toZDT: typeof toZDT;
toInstant: typeof toInstant;
_parseString: typeof _parseString;
_parseISO8601: typeof _parseISO8601;
nativeJs(date: any, zone?: time.ZoneId): time.ZonedDateTime;
Expand Down Expand Up @@ -114,6 +115,24 @@ declare function javaZDTToJsZDT(zdt: JavaZonedDateTime): time.ZonedDateTime;
* @throws error if the type, format, or contents of when are not supported
*/
declare function toZDT(when?: any): time.ZonedDateTime;
/**
* Converts the passed in when to a time.Instant based on the following
* set of rules.
*
* - null, undefined: time.Instant.now()
* - time.Instant: unmodified
* - time.ZonedDateTime: converted to the time.Instant equivalent
* - Java Instant, DateTimeType: converted to time.Instant equivalent
* - JavaScript native {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date Date}: converted to a `time.Instant`
* - Item: converts the state of the Item based on the *Type rules described here
* - String, Java String, StringType: parsed ISO Instant
* - RFC (output from a Java Instant.toString()): parsed to time.ZonedDateTime
* @memberof time
* @param {*} [when] any of the types discussed above
* @returns {time.Instant}
* @throws error if the type, format, or contents of when are not supported
*/
declare function toInstant(when?: any): time.Instant;
/**
* Parses the passed in string based on it's format and converts it to a ZonedDateTime.
* If no timezone is specified, the configured timezone is used.
Expand Down
2 changes: 1 addition & 1 deletion types/time.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit d02bb08

Please sign in to comment.