"use strict";
const utils = require("../utils");
const rust = require("../../index");
/** @module types */
/**
* 2^31 days before unix epoch is -5877641-06-23. This is the first day that can be represented by this class.
* @const
*/
const dateCenter = Math.pow(2, 31);
/**
* Maximum number of days from 01.01.1970 supported by the class (i32::MAX in Rust).
* @const
*/
const maxDay = 2147483647;
/**
* Minimum number of days from 01.01.1970 supported by the class (i32::MIN in Rust).
* @const
*/
const minDay = -2147483648;
/**
* @const
*/
const millisecondsPerDay = 86400000;
/**
* A date without a time-zone in the ISO-8601 calendar system, such as 2010-08-05.
*
* LocalDate is an immutable object that represents a date, often viewed as year-month-day. For example, the value "1st October 2014" can be stored in a LocalDate.
*
* This class does not store or represent a time or time-zone. Instead, it is a description of the date, as used for birthdays.
* It cannot represent an instant on the time-line without additional information such as an offset or time-zone.
*
* Note that this type can represent dates in the range [-5877641-06-23; 5881580-07-17]
* while the ES5 date type can only represent values in the range of [-271821-04-20; 275760-09-13].
* In the event that year, month, day parameters do not fall within the ES5 date range an Error will be thrown.
* If you wish to represent a date outside of this range, pass a single
* parameter indicating the days since epoch. For example, -1 represents 1969-12-31.
* @property {Date} date The date representation if falls within a range of an ES5 data type, otherwise an invalid date.
*/
class LocalDate {
/**
* @type {rust.LocalTimeWrapper}
* @private
*/
#inner;
/**
* Creates a new instance of LocalDate.
* @param {Number} year The year or days since epoch. If days since epoch, month and day should not be provided.
* @param {Number} month Between 1 and 12 inclusive.
* @param {Number} day Between 1 and the number of days in the given month of the given year.
*/
constructor(year, month, day) {
// implementation detail: internally uses a UTC based date
if (
typeof year === "number" &&
typeof month === "number" &&
typeof day === "number"
) {
// It will throw an error if the values are wrong.
this.#inner = rust.LocalDateWrapper.new(day, month, year);
if (!this.#inner.inDate) {
throw new Error("You must provide a valid year, month and day");
}
} else if (
typeof year === "number" &&
typeof month === "undefined" &&
typeof day === "undefined"
) {
// In days since epoch.
if (year < minDay || year > maxDay) {
throw new Error(
`You must provide a valid value for days since epoch (${minDay} <= value <= ${maxDay}).`,
);
}
this.#inner = rust.LocalDateWrapper.newDay(year);
} else {
throw new Error("You must provide a valid year, month and day");
}
}
/**
* A number representing the year. May return NaN if cannot be represented as a Date.
* @readonly
* @type {Number}
*/
get year() {
return this.#inner.inDate ? this.#inner.date.year : NaN;
}
set year(_) {
throw new SyntaxError("LocalDate year is read-only");
}
/**
* A number between 1 and 12 inclusive representing the month.
* May return NaN if cannot be represented as a Date.
* @readonly
* @type {Number}
*/
get month() {
return this.#inner.inDate ? this.#inner.date.month : NaN;
}
set month(_) {
throw new SyntaxError("LocalDate month is read-only");
}
/**
* A number between 1 and the number of days in the given month of the given year (value up to 31).
* May return NaN if cannot be represented as a Date.
* @readonly
* @type {Number}
*/
get day() {
return this.#inner.inDate ? this.#inner.date.day : NaN;
}
set day(_) {
throw new SyntaxError("LocalDate day is read-only");
}
/**
* If date cannot be represented yet given a valid days since epoch, track it internally.
* @readonly
* @deprecated This member is in Datastax documentation, but it seems to not be exposed in the API.
* Additionally we added a new class member: `value` that always returns days since epoch regardless of the date.
* @type {Number}
*/
get _value() {
return this.#inner.inDate ? null : this.#inner.value;
}
set _value(_) {
throw new SyntaxError("LocalDate _value is read-only");
}
/**
* Always valid amount of days since epoch.
* @readonly
* @type {Number}
*/
get value() {
return this.#inner.value;
}
set value(_) {
throw new SyntaxError("LocalDate value is read-only");
}
/**
* Date object represent this date.
* @readonly
* @type {Date}
*/
get date() {
return new Date(this.#inner.value * millisecondsPerDay);
}
set date(_) {
throw new SyntaxError("LocalDate date is read-only");
}
/**
* Creates a new instance of LocalDate using the current year, month and day from the system clock in the default time-zone.
*/
static now() {
return LocalDate.fromDate(new Date());
}
/**
* Creates a new instance of LocalDate using the current date from the system clock at UTC.
*/
static utcNow() {
return LocalDate.fromDate(Date.now());
}
/**
* Creates a new instance of LocalDate using the year, month and day from the provided local date time.
* @param {Date} date
* @returns {LocalDate}
*/
static fromDate(date) {
if (isNaN(date.getTime())) {
throw new TypeError(`Invalid date: ${date}`);
}
return new LocalDate(
date.getFullYear(),
date.getMonth() + 1, // getMonth() returns the month index 0..11, and the need for a number 1..12
date.getDate(),
);
}
/**
* Creates a new instance of LocalDate using the year, month and day provided in the form: yyyy-mm-dd or
* days since epoch (i.e. -1 for Dec 31, 1969).
* @param {string} value
* @returns {LocalDate}
*/
static fromString(value) {
let days = rust.LocalDateWrapper.fromString(value);
return new LocalDate(days);
}
/**
* Creates a new instance of LocalDate using the bytes representation.
* @param {Buffer} buffer
* @returns {LocalDate}
*/
static fromBuffer(buffer) {
// move to unix epoch: 0.
return new LocalDate(buffer.readUInt32BE(0) - dateCenter);
}
/**
* Compares this LocalDate with the given one.
* @param {LocalDate} other date to compare against.
* @return {number} 0 if they are the same, 1 if the this is greater, and -1
* if the given one is greater.
*/
compare(other) {
if (this.value == other.value) {
return 0;
} else if (this.value > other.value) {
return 1;
}
return -1;
}
/**
* Returns true if the value of the LocalDate instance and other are the same
* @param {LocalDate} other
* @returns {Boolean}
*/
equals(other) {
return other instanceof LocalDate && this.compare(other) === 0;
}
/**
* Provide the name of the constructor and the string representation
* @returns {string}
*/
inspect() {
return `${this.constructor.name} : ${this.toString()}`;
}
/**
* Gets the bytes representation of the instance.
* @returns {Buffer}
*/
toBuffer() {
// days since unix epoch
const value = this.#inner.value + dateCenter;
const buf = utils.allocBufferUnsafe(4);
buf.writeUInt32BE(value, 0);
return buf;
}
/**
* Gets the string representation of the instance in the form: yyyy-mm-dd if
* the value can be parsed as a Date, otherwise days since epoch.
* @returns {string}
*/
toString() {
return this.#inner.toString();
}
/**
* Gets the string representation of the instance in the form: yyyy-mm-dd, valid for JSON.
* @returns {string}
*/
toJSON() {
return this.toString();
}
/**
* Get LocalDate from rust object.
* @package
* @param {rust.LocalDateWrapper} arg
* @returns {LocalDate}
*/
static fromRust(arg) {
return new LocalDate(arg.value);
}
/**
* @package
* @returns {rust.LocalDateWrapper}
*/
getInternal() {
return this.#inner;
}
}
module.exports = LocalDate;