Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide time.toInstant method #413

Merged
merged 3 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 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,23 @@ 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), the state is converted | `time.toInstant(items.getItem('MyItem'));` |
| `String`, `java.lang.String`, `StringType` | parsed | `time.toInstant('2019-10-12T07:20:50.52Z');` |
| `DateTimeType` | converted to the `time.Instant` equivalent | |

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
133 changes: 115 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,37 @@ 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 StringType) { // String type Items
return time.Instant.parse(rawState.toString());
} else if (rawState instanceof DateTimeType) { // DateTime Items
return javaInstantToJsInstant(rawState.getInstant());
} 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 +275,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 +302,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 +317,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 +329,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 +351,82 @@ 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
florian-h05 marked this conversation as resolved.
Show resolved Hide resolved
* - String, Java String, StringType: parsed
* @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 +593,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
18 changes: 18 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,23 @@ 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
* @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.

Loading