blob: 6fd2de30e921f8a6c6a99c3a0916adecb064ebd1 [file]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* TSDataType enum for Apache IoTDB data types
* Based on Apache TSFile type definitions
*
* @see https://github.com/apache/tsfile/blob/develop/java/common/src/main/java/org/apache/tsfile/enums/TSDataType.java
*/
export enum TSDataType {
/**
* Boolean type - stored as 1 byte (1 for true, 0 for false)
* JavaScript type: boolean
*/
BOOLEAN = 0,
/**
* 32-bit signed integer
* JavaScript type: number
* Storage size: 4 bytes
*/
INT32 = 1,
/**
* 64-bit signed integer
* JavaScript type: bigint (recommended) or number (may lose precision)
* Storage size: 8 bytes
*/
INT64 = 2,
/**
* 32-bit floating point number
* JavaScript type: number
* Storage size: 4 bytes
*/
FLOAT = 3,
/**
* 64-bit floating point number
* JavaScript type: number
* Storage size: 8 bytes
*/
DOUBLE = 4,
/**
* UTF-8 encoded text string
* JavaScript type: string
* Storage size: variable (4-byte length prefix + UTF-8 content)
*/
TEXT = 5,
// VECTOR = 6, // Reserved - not yet implemented
// UNKNOWN = 7, // Reserved - not yet implemented
/**
* Timestamp with millisecond precision
* JavaScript type: Date or number (milliseconds since epoch)
* Storage size: 8 bytes (stored as INT64)
*/
TIMESTAMP = 8,
/**
* Date with day precision (no time component)
* JavaScript type: Date or number (yyyyMMdd integer, e.g. 20240101)
* Storage size: 4 bytes (stored as INT32, encoded as year*10000 + month*100 + day)
*/
DATE = 9,
/**
* Binary large object (BLOB)
* JavaScript type: Buffer
* Storage size: variable (4-byte length prefix + binary content)
*/
BLOB = 10,
/**
* UTF-8 encoded string (similar to TEXT)
* JavaScript type: string
* Storage size: variable (4-byte length prefix + UTF-8 content)
*/
STRING = 11,
// OBJECT = 12, // Reserved - not yet implemented
}
/**
* Days per month (index 0 = January) in a non-leap year.
*/
const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
function isLeapYear(year: number): boolean {
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
}
/**
* Validate a calendar date, matching the Java client's DateUtils semantics:
* years are restricted to 1000-9999 and (year, month, day) must form a real
* calendar date (LocalDate would reject e.g. 2023-02-29).
*
* @throws Error if the components do not form a valid calendar date
*/
function validateCalendarDate(
year: number,
month: number,
day: number,
source: string,
): void {
if (year < 1000 || year > 9999) {
throw new Error(
`Invalid DATE value ${source}: year ${year} is out of range [1000, 9999]`,
);
}
if (month < 1 || month > 12) {
throw new Error(
`Invalid DATE value ${source}: month ${month} is out of range [1, 12]`,
);
}
const maxDay =
month === 2 && isLeapYear(year) ? 29 : DAYS_IN_MONTH[month - 1];
if (day < 1 || day > maxDay) {
throw new Error(
`Invalid DATE value ${source}: day ${day} is out of range [1, ${maxDay}] for ${year}-${String(month).padStart(2, "0")}`,
);
}
}
/**
* Convert a JavaScript Date (or an already-encoded yyyyMMdd number) to the
* IoTDB DATE wire format: an INT32 encoded as year*10000 + month*100 + day
* (e.g. 2024-01-01 -> 20240101).
*
* This matches the Java client's DateUtils.parseDateExpressionToInt and the
* C# client. The calendar date is taken from the Date's UTC components,
* consistent with `new Date("2024-01-01")` which parses as UTC midnight.
*
* Invalid inputs are rejected (like the Java client, which limits years to
* 1000-9999 and whose LocalDate guarantees a valid calendar date): invalid
* Date objects, non-finite/non-integer numbers, and numbers whose yyyyMMdd
* decomposition is not a real calendar date all throw.
*
* Note: null/undefined are not handled here — callers filter nulls before
* invoking this helper.
*
* @param value - Date object or a yyyyMMdd integer (validated, then passed
* through unchanged)
* @returns The yyyyMMdd integer encoding
* @throws Error if the value is not a valid calendar date
*/
export function parseDateToInt(value: Date | number): number {
if (value instanceof Date) {
if (isNaN(value.getTime())) {
throw new Error("Invalid DATE value: Date object is invalid (NaN time)");
}
const year = value.getUTCFullYear();
const month = value.getUTCMonth() + 1;
const day = value.getUTCDate();
validateCalendarDate(year, month, day, value.toISOString());
return year * 10000 + month * 100 + day;
}
if (!Number.isInteger(value)) {
throw new Error(
`Invalid DATE value ${value}: expected an integer in yyyyMMdd form (e.g. 20240101)`,
);
}
validateCalendarDate(
Math.trunc(value / 10000),
Math.trunc(value / 100) % 100,
value % 100,
String(value),
);
return value;
}
/**
* Convert an IoTDB DATE wire value (INT32, year*10000 + month*100 + day)
* back to a JavaScript Date at UTC midnight of that calendar date.
*
* Inverse of {@link parseDateToInt}; matches the Java client's
* DateUtils.parseIntToDate. The value is validated the same way as
* {@link parseDateToInt} — non-integer numbers, years outside 1000-9999,
* and impossible calendar dates (e.g. 20230229) throw instead of being
* silently normalized by the Date constructor.
*
* @param value - The yyyyMMdd integer (e.g. 20240101)
* @returns Date at UTC midnight of the encoded calendar date
* @throws Error if the value is not a valid yyyyMMdd calendar date
*/
export function parseIntToDate(value: number): Date {
if (!Number.isInteger(value)) {
throw new Error(
`Invalid DATE value ${value}: expected an integer in yyyyMMdd form (e.g. 20240101)`,
);
}
const year = Math.trunc(value / 10000);
const month = Math.trunc(value / 100) % 100;
const day = value % 100;
validateCalendarDate(year, month, day, String(value));
return new Date(Date.UTC(year, month - 1, day));
}
/**
* Get the name of a TSDataType from its numeric code
* @param typeCode - The numeric type code
* @returns The type name, or 'UNKNOWN' if not recognized
*/
export function getDataTypeName(typeCode: number): string {
switch (typeCode) {
case TSDataType.BOOLEAN:
return "BOOLEAN";
case TSDataType.INT32:
return "INT32";
case TSDataType.INT64:
return "INT64";
case TSDataType.FLOAT:
return "FLOAT";
case TSDataType.DOUBLE:
return "DOUBLE";
case TSDataType.TEXT:
return "TEXT";
case TSDataType.TIMESTAMP:
return "TIMESTAMP";
case TSDataType.DATE:
return "DATE";
case TSDataType.BLOB:
return "BLOB";
case TSDataType.STRING:
return "STRING";
default:
return "UNKNOWN";
}
}