UNPKG

@parischap/conversions

Version:

A functional library to replace partially the native Intl API

1,338 lines 84.2 kB
/** * This module implements an immutable `CVDateTime` object. * * `CVDateTime` objects keep an internal state. But all provided functions are pure insofar as they * always yield the same result whatever the state the object is in. The state is only used to * improve performance but does not alter the results. * * Unlike the Javascript `Date` objects and the Effect `DateTime` objects, `CVDateTime` objects * handle both the Gregorian and Iso calendars. So you can easily get/set the iso year and iso week * of a `CVDateTime` object. * * A `CVDateTime` object has a `zoneOffset` which is the difference in hours between the time in the * local zone and UTC time (e.g `zoneOffset=1` for timezone +1:00). All the data in a `CVDateTime` * object is `zoneOffset-dependent`, except `timestamp`. An important thing to note is that a * `CVDateTime` object with a timestamp `t` and a zoneOffset `zo` has exactly the same date parts * (`year`, `ordinalDay`, `month`, `monthDay`, `isoYear`...) as a `CVDateTime` object with * `timestamp = t+zox3600` and `zoneOffset = 0`. That's the reason for the _zonedTimestamp field * which is equal to `t+zox3600`. All calculations are performed UTC using _zonedTimestamp instead * of timestamp. */ import * as MArray from '@parischap/effect-lib/MArray'; import * as MFunction from '@parischap/effect-lib/MFunction'; import * as MInputError from '@parischap/effect-lib/MInputError'; import * as MInspectable from '@parischap/effect-lib/MInspectable'; import * as MNumber from '@parischap/effect-lib/MNumber'; import * as MPipeable from '@parischap/effect-lib/MPipeable'; import * as MStruct from '@parischap/effect-lib/MStruct'; import * as MTypes from '@parischap/effect-lib/MTypes'; import * as DateTime from 'effect/DateTime'; import * as Either from 'effect/Either'; import * as Equal from 'effect/Equal'; import * as Function from 'effect/Function'; import * as Hash from 'effect/Hash'; import * as Number from 'effect/Number'; import * as Option from 'effect/Option'; import * as Predicate from 'effect/Predicate'; import * as Struct from 'effect/Struct'; import {flow} from 'effect/Function'; import {pipe} from 'effect/Function'; import * as CVNumberBase10Format from './NumberBase10Format.js'; import * as CVTemplate from './Template.js'; import * as CVTemplatePlaceholder from './TemplatePlaceholder.js'; import * as CVTemplateSeparator from './TemplateSeparator.js'; /** * Module tag * * @category Module markers */ export const moduleTag = '@parischap/conversions/DateTime/'; const _TypeId = /*#__PURE__*/Symbol.for(moduleTag); /** * Duration of a second in milliseconds * * @category Constants */ export const SECOND_MS = 1_000; /** * Duration of a minute in milliseconds * * @category Constants */ export const MINUTE_MS = 60 * SECOND_MS; /** * Duration of an hour in milliseconds * * @category Constants */ export const HOUR_MS = 60 * MINUTE_MS; /** * Duration of a day in milliseconds * * @category Constants */ export const DAY_MS = 24 * HOUR_MS; /** * Duration of a week in milliseconds * * @category Constants */ export const WEEK_MS = 7 * DAY_MS; /** * Duration of a normal year in milliseconds * * @category Constants */ export const COMMON_YEAR_MS = 365 * DAY_MS; /** * Duration of a leap year in milliseconds * * @category Constants */ export const LEAP_YEAR_MS = COMMON_YEAR_MS + DAY_MS; /** * Duration of a short iso year in milliseconds * * @category Constants */ export const SHORT_YEAR_MS = 52 * WEEK_MS; /** * Duration of a long iso year in milliseconds * * @category Constants */ export const LONG_YEAR_MS = SHORT_YEAR_MS + WEEK_MS; /** * Local time zone offset in hours of the machine on which this code runs. The value is calculated * once at startup. * * @category Constants */ export const LOCAL_TIME_ZONE_OFFSET = -(/*#__PURE__*/new Date().getTimezoneOffset() / 60); /** * Namespace for the data relative to a Month * * @category Models */ const MAX_FULL_YEAR_OFFSET = 273_790; /** * Maximal usable year (ECMA-262) * * @category Constants */ export const MAX_FULL_YEAR = 1970 + MAX_FULL_YEAR_OFFSET; /** * Minimal usable year (ECMA-262) * * @category Constants */ export const MIN_FULL_YEAR = 1970 - MAX_FULL_YEAR_OFFSET - 1; /** * Maximal usable timestamp (ECMA-262) * * @category Constants */ export const MAX_TIMESTAMP = 8_640_000_000_000_000; /** * Minimal usable timestamp (ECMA-262) * * @category Constants */ export const MIN_TIMESTAMP = -MAX_TIMESTAMP; const _integer = CVNumberBase10Format.integer; const _params = { fillChar: '0', numberBase10Format: _integer }; const _fixedLengthToReal = CVTemplatePlaceholder.fixedLengthToReal; const _sep = CVTemplateSeparator; /** * Namespace for the a Gregorian date * * It is important to note that the Gregorian calendar is periodic with a 400-year period as far as * leap years are concerned. Leap years are those that can be divided by 4, except those that can be * divided by 100 except those that can be divided by 400. So 2100, 2200, 2300 are not leap years. * But 2400 is a leap year. * * @category Models */ var GregorianDate; (function (GregorianDate) { const _namespaceTag = moduleTag + 'GregorianDate/'; const _TypeId = /*#__PURE__*/Symbol.for(_namespaceTag); /** * Duration in milliseconds of a four-year period containing a leap year * * @category Constants */ const FOUR_YEARS_MS = 3 * COMMON_YEAR_MS + LEAP_YEAR_MS; /** * Duration in milliseconds of a 100-year period that has a leap year every 4th year except the * 100th year * * @category Constants */ const HUNDRED_YEARS_MS = 25 * FOUR_YEARS_MS - DAY_MS; /** * Duration in milliseconds of a 400-year period that has a leap year every 4th year except the * 100th year. But the 400th year is a leap year * * @category Constants */ const FOUR_HUNDRED_YEARS_MS = 4 * HUNDRED_YEARS_MS + DAY_MS; /** Timestamp of 1/1/2001 00:00:00:000+0:00 */ const YEAR_START_2001_MS = 978_307_200_000; /** Number of days in each month of a leap year */ const LEAP_YEAR_DAYS_IN_MONTH = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; /** Number of days in each month of a leap year */ const COMMON_YEAR_DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; /** * Type guard * * @category Guards */ GregorianDate.has = u => Predicate.hasProperty(u, _TypeId); /** Proto */ const proto = { [_TypeId]: _TypeId, ... /*#__PURE__*/MInspectable.BaseProto(_namespaceTag), ...MPipeable.BaseProto }; /** Constructor */ const _make = params => MTypes.objectFromDataAndProto(proto, params); const _makeWithInternals = params => _make({ ...params, _daysInMonth: params.yearIsLeap ? LEAP_YEAR_DAYS_IN_MONTH : COMMON_YEAR_DAYS_IN_MONTH }); /** * Constructs a GregorianDate from a timestamp * * @category Constructors */ GregorianDate.fromTimestamp = timestamp => { /** * The 100-year periods [2001, 2100], [2101, 2200], and [2201, 2300] all last HUNDRED_YEARS_MS. * Those three 100-year periods can be divided in 24 periods that last FOUR_YEARS_MS * (4xCOMMON_YEAR_MS + DAY_MS) and a final 4-year period that lasts FOUR_YEARS_MS - DAY_MS * (4xCOMMON_YEAR_MS). * * The 100-year period [2301, 2400] lasts HUNDRED_YEARS_MS + DAY_MS. This period can be divided * in 25 periods that last FOUR_YEARS_MS (4xCOMMON_YEAR_MS + DAY_MS). */ const offset2001 = timestamp - YEAR_START_2001_MS; const q400Years = Math.floor(offset2001 / FOUR_HUNDRED_YEARS_MS); const offset400Years = q400Years * FOUR_HUNDRED_YEARS_MS; const r400Years = offset2001 - offset400Years; // q100Years is equal to 4 on the last day of the 400-year period. const q100Years = Math.min(3, Math.floor(r400Years / HUNDRED_YEARS_MS)); const offset100Years = q100Years * HUNDRED_YEARS_MS; // r100Years is superior to HUNDRED_YEARS_MS on the last day of the 400-year period const r100Years = r400Years - offset100Years; const q4Years = Math.floor(r100Years / FOUR_YEARS_MS); const offset4Years = q4Years * FOUR_YEARS_MS; const r4Years = r100Years - offset4Years; // q1Year is equal to 4 on the last day of each 4-year period except the last day of years 2100, 2200 and 2300. const q1Year = Math.min(3, Math.floor(r4Years / COMMON_YEAR_MS)); const offset1Year = q1Year * COMMON_YEAR_MS; const yearIsLeap = q1Year === 3 && (q4Years !== 24 || q100Years === 3); const yearStartTimestamp = YEAR_START_2001_MS + offset400Years + offset100Years + offset4Years + offset1Year; return _makeWithInternals({ timestamp, year: 2001 + 400 * q400Years + 100 * q100Years + 4 * q4Years + q1Year, yearIsLeap, yearStartTimestamp, ordinalDay: Math.floor((timestamp - yearStartTimestamp) / DAY_MS) + 1, month: Option.none(), monthDay: Option.none() }); }; /** * If possible, returns a new GregorianDate having `year` set to `year` and the same `month` and * `monthDay` as `self`. Returns a left of an error otherwise. `year` must be an integer comprised * in the range [MIN_FULL_YEAR, MAX_FULL_YEAR]. If `self` represents a 29th of february, `year` * must be a leap year. * * @category Setters */ GregorianDate.setYear = year => self => Either.gen(function* () { const validatedYear = yield* pipe(year, MInputError.assertInRange({ min: MIN_FULL_YEAR, max: MAX_FULL_YEAR, minIncluded: true, maxIncluded: true, offset: 0, name: "'year'" })); const offset2001 = validatedYear - 2001; const q400Years = Math.floor(offset2001 / 400); const r400Years = offset2001 - 400 * q400Years; const q100Years = Math.floor(r400Years / 100); const r100Years = r400Years - 100 * q100Years; const q4Years = Math.floor(r100Years / 4); const r4Years = r100Years - 4 * q4Years; const yearIsLeap = r4Years === 3 && (r100Years !== 99 || r400Years === 399); const yearStartTimestamp = YEAR_START_2001_MS + q400Years * FOUR_HUNDRED_YEARS_MS + q100Years * HUNDRED_YEARS_MS + q4Years * FOUR_YEARS_MS + r4Years * COMMON_YEAR_MS; const selfYearIsLeap = self.yearIsLeap; const selfOrdinalDay = self.ordinalDay; const ordinalDayOffset = yield* selfYearIsLeap === yearIsLeap || selfOrdinalDay < 60 ? Either.right(0) : selfYearIsLeap ? selfOrdinalDay === 60 ? Either.left(new MInputError.Type({ message: `No February 29th on year ${year} which is not a leap year` })) : Either.right(-1) : Either.right(1); return _makeWithInternals({ timestamp: self.timestamp + yearStartTimestamp - self.yearStartTimestamp + ordinalDayOffset * DAY_MS, year: validatedYear, yearIsLeap, yearStartTimestamp, ordinalDay: selfOrdinalDay + ordinalDayOffset, month: self.month, monthDay: self.monthDay }); }); /** * If possible, returns a new GregorianDate having `month` set to `month` and the same `year` and * `monthDay` as `self`. Returns a left of an error otherwise. `month` must be an integer greater * than or equal to 1 (January) and less than or equal to 12 (December). `month` must also have at * least `monthDay` days. * * @category Setters */ GregorianDate.setMonth = month => self => Either.gen(function* () { const validatedMonth = yield* pipe(month, MInputError.assertInRange({ min: 1, max: 12, minIncluded: true, maxIncluded: true, offset: 0, name: "'month'" })); const monthDay = yield* pipe(self, GregorianDate.getMonthDay, Either.liftPredicate(Predicate.or(Number.lessThanOrEqualTo(28), Number.lessThanOrEqualTo(GregorianDate.getNumberOfDaysInMonth(validatedMonth)(self))), selfMonthDay => new MInputError.Type({ message: `Month ${month} of year ${self.year} does not have ${selfMonthDay} days` }))); const ordinalDay = GregorianDate.getMonthOffset(validatedMonth)(self) + monthDay; return pipe(self, MStruct.append({ timestamp: self.timestamp + (ordinalDay - self.ordinalDay) * DAY_MS, ordinalDay, month: Option.some(validatedMonth) }), _make); }); /** * If possible, returns a new GregorianDate having `monthDay` set to `monthDay` and the same * `year` and `month` as `self`. Returns a left of an error otherwise. `monthDay` must be an * integer greater than or equal to 1 and less than or equal to the number of days in the current * month. * * @category Setters */ GregorianDate.setMonthDay = monthDay => self => Either.gen(function* () { const validatedMonthDay = monthDay <= 28 ? monthDay : yield* pipe(monthDay, MInputError.assertInRange({ min: 1, max: GregorianDate.getNumberOfDaysInMonth(GregorianDate.getMonth(self))(self), minIncluded: true, maxIncluded: true, offset: 0, name: "'monthDay'" })); const ordinalDayOffset = validatedMonthDay - GregorianDate.getMonthDay(self); return pipe(self, MStruct.append({ timestamp: self.timestamp + ordinalDayOffset * DAY_MS, ordinalDay: self.ordinalDay + ordinalDayOffset, monthDay: Option.some(validatedMonthDay) }), _make); }); /** * If possible, returns a new GregorianDate having `ordinalDay` set to `ordinalDay` and the same * `year` as `self`. Returns a left of an error otherwise. `ordinalDay` must be an integer greater * than or equal to 1 and less than or equal to the number of days in the current year * * @category Setters */ GregorianDate.setOrdinalDay = ordinalDay => self => Either.gen(function* () { const validatedOrdinalDay = yield* pipe(ordinalDay, MInputError.assertInRange({ min: 1, max: GregorianDate.getYearDurationInDays(self), minIncluded: true, maxIncluded: true, offset: 0, name: "'ordinalDay'" })); return pipe(self, MStruct.append({ timestamp: self.timestamp + (validatedOrdinalDay - self.ordinalDay) * DAY_MS, ordinalDay: validatedOrdinalDay, month: Option.none(), monthDay: Option.none() }), _make); }); /** * Returns the `timestamp` property of `self` * * @category Destructors */ GregorianDate.timestamp = /*#__PURE__*/Struct.get('timestamp'); /** * Returns the `year` property of `self` * * @category Destructors */ GregorianDate.year = /*#__PURE__*/Struct.get('year'); /** * Returns the `yearIsLeap` property of `self` * * @category Predicates */ GregorianDate.yearIsLeap = /*#__PURE__*/Struct.get('yearIsLeap'); /** * Returns the `yearStartTimestamp` property of `self` * * @category Destructors */ GregorianDate.yearStartTimestamp = /*#__PURE__*/Struct.get('yearStartTimestamp'); /** * Returns the `ordinalDay` property of `self` * * @category Destructors */ GregorianDate.ordinalDay = /*#__PURE__*/Struct.get('ordinalDay'); /** * Returns the `month` of `self` * * @category Destructors */ GregorianDate.getMonth = self => pipe(self.month, Option.getOrElse(() => { const ordinalDay = self.ordinalDay; const yearIsLeap = self.yearIsLeap; const adjustedOrdinalDay = ordinalDay - (yearIsLeap ? 1 : 0); const result = ordinalDay <= 31 ? 1 : adjustedOrdinalDay <= 59 ? 2 : Math.floor((adjustedOrdinalDay - 59) / 30.6 - 0.018) + 3; /* eslint-disable-next-line functional/immutable-data, functional/no-expression-statements */ self.month = Option.some(result); return result; })); /** * Returns the `monthDay` of `self` * * @category Destructors */ GregorianDate.getMonthDay = self => pipe(self.monthDay, Option.getOrElse(() => { const result = self.ordinalDay - GregorianDate.getMonthOffset(GregorianDate.getMonth(self))(self); /* eslint-disable-next-line functional/immutable-data, functional/no-expression-statements */ self.monthDay = Option.some(result); return result; })); /** * Returns the duration of the year described by `self` in milliseconds * * @category Destructors */ GregorianDate.getYearDurationInMs = self => self.yearIsLeap ? LEAP_YEAR_MS : COMMON_YEAR_MS; /** * Returns the duration of the year described by `self` in days * * @category Destructors */ GregorianDate.getYearDurationInDays = self => self.yearIsLeap ? 366 : 365; /** * Returns the number of days from the start of the `year` property of `self` to the day before * the first day of month `month` * * @category Destructors */ GregorianDate.getMonthOffset = month => self => month === 1 ? 0 : month === 2 ? 31 : 30 * (month - 1) + Math.floor(0.6 * (month + 1)) - (self.yearIsLeap ? 2 : 3); /** * Returns the number of days of month `month` of the `year` property of `self` * * @category Destructors */ GregorianDate.getNumberOfDaysInMonth = month => flow(Struct.get('_daysInMonth'), MArray.unsafeGet(month - 1)); const _formatter = /*#__PURE__*/flow(/*#__PURE__*/CVTemplate.toFormatter(/*#__PURE__*/CVTemplate.make(/*#__PURE__*/_fixedLengthToReal({ ..._params, name: 'year', length: 4 }), _sep.hyphen, /*#__PURE__*/_fixedLengthToReal({ ..._params, name: 'month', length: 2 }), _sep.hyphen, /*#__PURE__*/_fixedLengthToReal({ ..._params, name: 'monthDay', length: 2 }))), /*#__PURE__*/Either.getOrThrowWith(Function.identity)); /** * Returns the ISO representation of this Gregorian Date * * @category Destructors */ GregorianDate.getIsoString = /*#__PURE__*/flow(/*#__PURE__*/MStruct.enrichWith({ month: GregorianDate.getMonth, monthDay: GregorianDate.getMonthDay }), _formatter); })(GregorianDate || (GregorianDate = {})); /** * Namespace for an IsoDate. * * An iso year starts on the first day of the first iso week. An iso week starts on a monday and * ends on a sunday. The first iso week of the year is the one that contains January 4th (see * Wikipedia). * * @category Models */ var IsoDate; (function (IsoDate) { const _namespaceTag = moduleTag + 'IsoDate/'; const _TypeId = /*#__PURE__*/Symbol.for(_namespaceTag); /** * Duration in milliseconds of a 6-iso-year period comprised of 1 long year and 5 short years (see * Wikipedia) * * @category Constants */ const SIX_YEARS_MS = LONG_YEAR_MS + 5 * SHORT_YEAR_MS; /** * Duration in milliseconds of an 11-iso-year period comprised of 2 long years and 9 short years * (see Wikipedia) * * @category Constants */ const ELEVEN_YEARS_MS = 2 * LONG_YEAR_MS + 9 * SHORT_YEAR_MS; /** * Duration in milliseconds of a 28-iso-year period comprised of 5 long years and 23 short years * (see Wikipedia) * * @category Constants */ const TWENTY_EIGHT_YEARS_MS = 5 * LONG_YEAR_MS + 23 * SHORT_YEAR_MS; /** * Duration in milliseconds of a 96-iso-year period comprised of 17 long years and 79 short years * (see Wikipedia) * * @category Constants */ const NINETY_SIX_YEARS_MS = 17 * LONG_YEAR_MS + 79 * SHORT_YEAR_MS; /** * Duration in milliseconds of a 100-iso-year period comprised of 18 long years and 82 short years * (see Wikipedia) * * @category Constants */ const ONE_HUNDRED_YEARS_MS = 18 * LONG_YEAR_MS + 82 * SHORT_YEAR_MS; /** * Duration in milliseconds of a 400-iso-year period comprised of 71 long years and 329 short * years (see Wikipedia) * * @category Constants */ const FOUR_HUNDRED_YEARS_MS = 71 * LONG_YEAR_MS + 329 * SHORT_YEAR_MS; /** * Timestamp of 03/01/2000 00:00:00:000+0:00 * * @category Constants */ const YEAR_START_2000_MS = 946_857_600_000; /** * Timestamp of 04/01/2010 00:00:00:000+0:00 * * @category Constants */ const YEAR_START_2010_MS = 1_262_563_200_000; /** * Type guard * * @category Guards */ IsoDate.has = u => Predicate.hasProperty(u, _TypeId); /** Proto */ const proto = { [_TypeId]: _TypeId, ... /*#__PURE__*/MInspectable.BaseProto(_namespaceTag), ...MPipeable.BaseProto }; /** Constructor */ const _make = params => MTypes.objectFromDataAndProto(proto, params); /** * Constructs an IsoDate from a timestamp * * @category Constructors */ IsoDate.fromTimestamp = timestamp => { const offset = timestamp - YEAR_START_2000_MS; const q400Years = Math.floor(offset / FOUR_HUNDRED_YEARS_MS); const r400Years = offset - q400Years * FOUR_HUNDRED_YEARS_MS; // The second one-hundred year period is a week shorter because it has 17 long years instead of 18 // Also the hundred-th year must be put in the first one-hundred year period because it is not long const q100Years = r400Years < ONE_HUNDRED_YEARS_MS + SHORT_YEAR_MS ? 0 : Math.floor((r400Years + WEEK_MS) / ONE_HUNDRED_YEARS_MS); const adjustedR400Years = r400Years - q100Years * NINETY_SIX_YEARS_MS + SHORT_YEAR_MS; const q28Years = Math.floor(adjustedR400Years / TWENTY_EIGHT_YEARS_MS); const r28Years = adjustedR400Years - q28Years * TWENTY_EIGHT_YEARS_MS; const adjustedR28Years = r28Years - ELEVEN_YEARS_MS; const q11Years = Math.floor(adjustedR28Years / ELEVEN_YEARS_MS); const r11Years = adjustedR28Years - q11Years * ELEVEN_YEARS_MS; const q6Years = Math.floor(r11Years / SIX_YEARS_MS); const r6Years = r11Years - q6Years * SIX_YEARS_MS; const isFirstSixYearPeriod = q6Years === 0; const q1Year = Math.min(Math.floor(r6Years / SHORT_YEAR_MS), isFirstSixYearPeriod ? 5 : 4); //console.log(q400Years, q100Years, q28Years, q11Years, q6Years, q1Year); return _make({ timestamp, year: 2010 + q400Years * 400 + q100Years * 96 + q28Years * 28 + q11Years * 11 + q6Years * 6 + q1Year, yearStartTimestamp: YEAR_START_2010_MS + q400Years * FOUR_HUNDRED_YEARS_MS + q100Years * NINETY_SIX_YEARS_MS + q28Years * TWENTY_EIGHT_YEARS_MS + q11Years * ELEVEN_YEARS_MS + q6Years * SIX_YEARS_MS + q1Year * SHORT_YEAR_MS, yearIsLong: isFirstSixYearPeriod && q1Year == 5 || !isFirstSixYearPeriod && q1Year == 4, isoWeek: Option.none(), weekday: Option.none() }); }; /** * Constructs an IsoDate from a GregorianDate * * @category Constructors */ IsoDate.fromGregorianDate = gregorianDate => { // 0 is friday, 6 is thursday const yearStartWeekday = MNumber.intModulo(7)(Math.floor((gregorianDate.yearStartTimestamp - DAY_MS) / DAY_MS)); const yearIsLeap = gregorianDate.yearIsLeap; const minOrdinalDayIndex = 3 - yearStartWeekday; const ordinalDay = gregorianDate.ordinalDay; if (ordinalDay <= minOrdinalDayIndex) { const year = gregorianDate.year - 1; const yearIsLong = yearStartWeekday === 0 || yearStartWeekday === 1 && !yearIsLeap && (year % 4 == 0 && year % 100 != 0 || year % 400 == 0); return _make({ timestamp: gregorianDate.timestamp, year, yearStartTimestamp: gregorianDate.yearStartTimestamp + (minOrdinalDayIndex - (yearIsLong ? 371 : 364)) * DAY_MS, yearIsLong, isoWeek: Option.none(), weekday: Option.none() }); } const yearIsLong = yearStartWeekday === 6 || yearStartWeekday === 5 && yearIsLeap; const maxOrdinalDay = minOrdinalDayIndex + (yearIsLong ? 371 : 364); if (ordinalDay > maxOrdinalDay) { const year = gregorianDate.year + 1; const yearIsLong = yearIsLeap ? yearStartWeekday === 4 : yearStartWeekday === 5 || yearStartWeekday === 4 && (year % 4 == 0 && year % 100 != 0 || year % 400 == 0); return _make({ timestamp: gregorianDate.timestamp, year, yearStartTimestamp: gregorianDate.yearStartTimestamp + maxOrdinalDay * DAY_MS, yearIsLong, isoWeek: Option.none(), weekday: Option.none() }); } return _make({ timestamp: gregorianDate.timestamp, year: gregorianDate.year, yearStartTimestamp: gregorianDate.yearStartTimestamp + minOrdinalDayIndex * DAY_MS, yearIsLong, isoWeek: Option.none(), weekday: Option.none() }); }; /** * If possible, returns a new IsoDate having `year` set to `year` and the same `isoWeek` and * `weekday` as `self`. Returns a left of an error otherwise. `year` must be an integer comprised * in the range [MIN_FULL_YEAR, MAX_FULL_YEAR]. If the isoWeek of `self` is equal to 53, `year` * must be a long year. * * @category Setters */ IsoDate.setYear = year => self => Either.gen(function* () { const validatedYear = yield* pipe(year, MInputError.assertInRange({ min: MIN_FULL_YEAR, max: MAX_FULL_YEAR, minIncluded: true, maxIncluded: true, offset: 0, name: "'year'" })); const offset = validatedYear - 2000; const q400Years = Math.floor(offset / 400); const r400Years = offset - q400Years * 400; // year 100 needs to be treated in the first one-hundred year period because it is not a long year const q100Years = r400Years === 100 ? 0 : Math.floor(r400Years / 100); const adjustedR400Years = r400Years - q100Years * 96 + 1; const q28Years = Math.floor(adjustedR400Years / 28); const r28Years = adjustedR400Years - q28Years * 28; const adjustedR28Years = r28Years - 11; const q11Years = Math.floor(adjustedR28Years / 11); const r11Years = adjustedR28Years - q11Years * 11; const yearStartTimestamp = YEAR_START_2010_MS + q400Years * FOUR_HUNDRED_YEARS_MS + q100Years * NINETY_SIX_YEARS_MS + q28Years * TWENTY_EIGHT_YEARS_MS + q11Years * ELEVEN_YEARS_MS + r11Years * SHORT_YEAR_MS + (r11Years > 5 ? WEEK_MS : 0); return yield* pipe(_make({ timestamp: self.timestamp + yearStartTimestamp - self.yearStartTimestamp, year: validatedYear, yearStartTimestamp, yearIsLong: r11Years === 5 || r11Years === 10, isoWeek: Option.some(IsoDate.getIsoWeek(self)), weekday: Option.some(IsoDate.getWeekday(self)) }), Either.liftPredicate(Predicate.or(IsoDate.yearIsLong, flow(IsoDate.getIsoWeek, Number.lessThan(53))), () => new MInputError.Type({ message: `No 53rd week on iso year ${year} which is not a short year` }))); }); /** * If possible, returns a new IsoDate having `isoWeek` set to `isoWeek` and the same `year` and * `weekday` as `self`. Returns a left of an error otherwise. `isoWeek` must be an integer greater * than or equal to 1 and less than or equal to the number of iso weeks in the current year. * * @category Setters */ IsoDate.setIsoWeek = isoWeek => self => Either.gen(function* () { const validatedIsoWeek = yield* pipe(isoWeek, MInputError.assertInRange({ min: 1, max: IsoDate.getLastIsoWeek(self), minIncluded: true, maxIncluded: true, offset: 0, name: "'isoWeek'" })); const offset = validatedIsoWeek - IsoDate.getIsoWeek(self); return pipe(self, MStruct.evolve({ timestamp: Number.sum(offset * WEEK_MS), isoWeek: pipe(validatedIsoWeek, Option.some, Function.constant) }), _make); }); /** * If possible, returns a new IsoDate having `weekday` set to `weekday` and the same `year` and * `isoWeek` as `self`. Returns a left of an error otherwise. `weekday` must be an integer greater * than or equal to 1 (monday) and less than or equal to 7 (sunday). * * @category Setters */ IsoDate.setWeekday = weekday => self => Either.gen(function* () { const validatedWeekday = yield* pipe(weekday, MInputError.assertInRange({ min: 1, max: 7, minIncluded: true, maxIncluded: true, offset: 0, name: "'weekday'" })); const offset = validatedWeekday - IsoDate.getWeekday(self); return pipe(self, MStruct.evolve({ timestamp: Number.sum(offset * DAY_MS), weekday: pipe(validatedWeekday, Option.some, Function.constant) }), _make); }); /** * Returns the `timestamp` property of `self` * * @category Destructors */ IsoDate.timestamp = /*#__PURE__*/Struct.get('timestamp'); /** * Returns the `year` property of `self` * * @category Destructors */ IsoDate.year = /*#__PURE__*/Struct.get('year'); /** * Returns the `yearStartTimestamp` property of `self` * * @category Destructors */ IsoDate.yearStartTimestamp = /*#__PURE__*/Struct.get('yearStartTimestamp'); /** * Returns the `yearIsLong` property of `self` * * @category Predicates */ IsoDate.yearIsLong = /*#__PURE__*/Struct.get('yearIsLong'); /** * Returns the `isoWeek` of `self` * * @category Destructors */ IsoDate.getIsoWeek = self => pipe(self.isoWeek, Option.getOrElse(() => { const result = Math.floor((self.timestamp - self.yearStartTimestamp) / WEEK_MS) + 1; /* eslint-disable-next-line functional/immutable-data, functional/no-expression-statements */ self.isoWeek = Option.some(result); return result; })); /** * Returns the `weekday` of `self` * * @category Destructors */ IsoDate.getWeekday = self => pipe(self.weekday, Option.getOrElse(() => { const result = Math.floor((self.timestamp - self.yearStartTimestamp - (IsoDate.getIsoWeek(self) - 1) * WEEK_MS) / DAY_MS) + 1; /* eslint-disable-next-line functional/immutable-data, functional/no-expression-statements */ self.weekday = Option.some(result); return result; })); /** * Returns the duration of the year described by `self` in milliseconds * * @category Destructors */ IsoDate.getMsDuration = self => self.yearIsLong ? LONG_YEAR_MS : SHORT_YEAR_MS; /** * Returns the duration of the year described by `self` in milliseconds * * @category Destructors */ IsoDate.getLastIsoWeek = self => self.yearIsLong ? 53 : 52; const _formatter = /*#__PURE__*/flow(/*#__PURE__*/CVTemplate.toFormatter(/*#__PURE__*/CVTemplate.make(/*#__PURE__*/_fixedLengthToReal({ ..._params, name: 'year', length: 4 }), /*#__PURE__*/_sep.make('-W'), /*#__PURE__*/_fixedLengthToReal({ ..._params, name: 'isoWeek', length: 2 }), _sep.hyphen, /*#__PURE__*/_fixedLengthToReal({ ..._params, name: 'weekday', length: 2 }))), /*#__PURE__*/Either.getOrThrowWith(Function.identity)); /** * Returns the ISO representation of this Gregorian Date * * @category Destructors */ IsoDate.getIsoString = /*#__PURE__*/flow(/*#__PURE__*/MStruct.enrichWith({ isoWeek: IsoDate.getIsoWeek, weekday: IsoDate.getWeekday }), _formatter); })(IsoDate || (IsoDate = {})); /** * Namespace for the data relative to the time * * @category Models */ var Time; (function (Time) { const _namespaceTag = moduleTag + 'Time/'; const _TypeId = /*#__PURE__*/Symbol.for(_namespaceTag); /** * Type guard * * @category Guards */ Time.has = u => Predicate.hasProperty(u, _TypeId); /** Proto */ const proto = { [_TypeId]: _TypeId, ... /*#__PURE__*/MInspectable.BaseProto(_namespaceTag), ...MPipeable.BaseProto }; /** Constructor */ const _make = params => MTypes.objectFromDataAndProto(proto, params); /** * Constructs the Time that corresponds to the passed `timestampOffset` which is the number of * milliseconds from the start of the current day * * @category Constructors */ Time.fromTimestamp = timestampOffset => { const hour23 = Math.floor(timestampOffset / HOUR_MS); const rHour23 = timestampOffset - hour23 * HOUR_MS; const [hour11, meridiem] = hour23 >= 12 ? [hour23 - 12, 12] : [hour23, 0]; const minute = Math.floor(rHour23 / MINUTE_MS); const rMinute = rHour23 - minute * MINUTE_MS; const second = Math.floor(rMinute / SECOND_MS); return _make({ timestampOffset, hour23, hour11, meridiem, minute, second, millisecond: rMinute - second * SECOND_MS }); }; /** * If possible, returns a right of a copy of `self` with `hour23` set to `hour23`. Returns a left * of an error otherwise. `hour23` must be an integer greater than or equal to 0 and less than or * equal to 23 * * @category Setters */ Time.setHour23 = hour23 => self => Either.gen(function* () { const validatedHour23 = yield* pipe(hour23, MInputError.assertInRange({ min: 0, max: 23, minIncluded: true, maxIncluded: true, offset: 0, name: "'hour23'" })); const isPast12 = validatedHour23 >= 12; return _make({ ...self, timestampOffset: self.timestampOffset + (validatedHour23 - self.hour23) * HOUR_MS, hour23: validatedHour23, hour11: isPast12 ? hour23 - 12 : hour23, meridiem: isPast12 ? 12 : 0 }); }); /** * If possible, returns a right of a copy of `self` with `hour11` set to `hour11`. Returns a left * of an error otherwise. `hour11` must be an integer greater than or equal to 0 and less than or * equal to 11 * * @category Setters */ Time.setHour11 = hour11 => self => Either.gen(function* () { const validatedHour11 = yield* pipe(hour11, MInputError.assertInRange({ min: 0, max: 11, minIncluded: true, maxIncluded: true, offset: 0, name: "'hour11'" })); const validatedHour23 = self.meridiem + validatedHour11; return _make({ ...self, timestampOffset: self.timestampOffset + (validatedHour23 - self.hour23) * HOUR_MS, hour23: validatedHour23, hour11: validatedHour11 }); }); /** * Returns a copy of `self` with `meridiem` set to `merdiem`. * * @category Setters */ Time.setMeridiem = meridiem => self => { const validatedHour23 = self.hour11 + meridiem; return _make({ ...self, timestampOffset: self.timestampOffset + (validatedHour23 - self.hour23) * HOUR_MS, hour23: validatedHour23, meridiem }); }; /** * If possible, returns a right of a copy of `self` with `minute` set to `minute`. Returns a left * of an error otherwise. `minute` must be an integer greater than or equal to 0 and less than or * equal to 59 * * @category Setters */ Time.setMinute = minute => self => Either.gen(function* () { const validatedMinute = yield* pipe(minute, MInputError.assertInRange({ min: 0, max: 59, minIncluded: true, maxIncluded: true, offset: 0, name: "'minute'" })); return _make({ ...self, timestampOffset: self.timestampOffset + (validatedMinute - self.minute) * MINUTE_MS, minute: validatedMinute }); }); /** * If possible, returns a right of a copy of `self` with `second` set to `second`. Returns a left * of an error otherwise. `second` must be an integer greater than or equal to 0 and less than or * equal to 59 * * @category Setters */ Time.setSecond = second => self => Either.gen(function* () { const validatedSecond = yield* pipe(second, MInputError.assertInRange({ min: 0, max: 59, minIncluded: true, maxIncluded: true, offset: 0, name: "'second'" })); return _make({ ...self, timestampOffset: self.timestampOffset + (validatedSecond - self.second) * SECOND_MS, second: validatedSecond }); }); /** * If possible, returns a right of a copy of `self` with `millisecond` set to `millisecond`. * Returns a left of an error otherwise. `millisecond` must be an integer greater than or equal to * 0 and less than or equal to 999 * * @category Setters */ Time.setMillisecond = millisecond => self => Either.gen(function* () { const validatedMillisecond = yield* pipe(millisecond, MInputError.assertInRange({ min: 0, max: 999, minIncluded: true, maxIncluded: true, offset: 0, name: "'millisecond'" })); return _make({ ...self, timestampOffset: self.timestampOffset + validatedMillisecond - self.millisecond, millisecond: validatedMillisecond }); }); /** * Returns the `timestampOffset` property of `self` * * @category Destructors */ Time.timestampOffset = /*#__PURE__*/Struct.get('timestampOffset'); /** * Returns the `hour23` property of `self` * * @category Destructors */ Time.hour23 = /*#__PURE__*/Struct.get('hour23'); /** * Returns the `hour11` property of `self` * * @category Destructors */ Time.hour11 = /*#__PURE__*/Struct.get('hour11'); /** * Returns the `meridiem` property of `self` * * @category Destructors */ Time.meridiem = /*#__PURE__*/Struct.get('meridiem'); /** * Returns the `minute` property of `self` * * @category Destructors */ Time.minute = /*#__PURE__*/Struct.get('minute'); /** * Returns the `second` property of `self` * * @category Destructors */ Time.second = /*#__PURE__*/Struct.get('second'); /** * Returns the `millisecond` property of `self` * * @category Destructors */ Time.millisecond = /*#__PURE__*/Struct.get('millisecond'); const _formatter = /*#__PURE__*/flow(/*#__PURE__*/CVTemplate.toFormatter(/*#__PURE__*/CVTemplate.make(/*#__PURE__*/_fixedLengthToReal({ ..._params, name: 'hour23', length: 2 }), _sep.colon, /*#__PURE__*/_fixedLengthToReal({ ..._params, name: 'minute', length: 2 }), _sep.colon, /*#__PURE__*/_fixedLengthToReal({ ..._params, name: 'second', length: 2 }), _sep.dot, /*#__PURE__*/_fixedLengthToReal({ ..._params, name: 'millisecond', length: 3 }))), /*#__PURE__*/Either.getOrThrowWith(Function.identity)); /** * Returns the ISO representation of this Gregorian Date * * @category Destructors */ Time.getIsoString = _formatter; })(Time || (Time = {})); /** * Namespace for the data relative to the parts of a zone offset * * @category Models */ var ZoneOffsetParts; (function (ZoneOffsetParts) { const _namespaceTag = moduleTag + 'ZoneOffsetParts/'; const _TypeId = /*#__PURE__*/Symbol.for(_namespaceTag); /** * Type guard * * @category Guards */ ZoneOffsetParts.has = u => Predicate.hasProperty(u, _TypeId); /** Proto */ const proto = { [_TypeId]: _TypeId, ... /*#__PURE__*/MInspectable.BaseProto(_namespaceTag), ...MPipeable.BaseProto }; ZoneOffsetParts._make = params => MTypes.objectFromDataAndProto(proto, params); /** * Builds a ZoneOffsetParts from `zoneOffset` * * @category Constructors */ ZoneOffsetParts.fromZoneOffset = zoneOffset => { const zoneHour = Math.trunc(zoneOffset); const minutesSeconds = Math.abs(zoneOffset - zoneHour) * 60; const zoneMinute = Math.trunc(minutesSeconds); const zoneSecond = Math.trunc((minutesSeconds - zoneMinute) * 60); return ZoneOffsetParts._make({ zoneHour, zoneMinute, zoneSecond }); }; /** * Tries to build a ZoneOffsetParts from `zoneHour`, `zoneMinute`, `zoneSecond`. Returns a `some` * if successful. A `none` otherwise. * * - `zoneHour` must be greater than or equal to -12 and less than or equal to 14. * - `zoneMinute` must be greater than or equal to 0 and less than or equal to 59. * - `zoneSecond` must be greater than or equal to 0 and less than or equal to 59. * * @category Constructors */ ZoneOffsetParts.fromParts = ({ zoneHour, zoneMinute, zoneSecond }) => Either.gen(function* () { const validatedHour = yield* pipe(zoneHour, MInputError.assertInRange({ min: -12, max: 14, minIncluded: true, maxIncluded: true, offset: 0, name: "'zoneHour'" })); const validatedMinute = yield* pipe(zoneMinute, MInputError.assertInRange({ min: 0, max: 59, minIncluded: true, maxIncluded: true, offset: 0, name: "'zoneMinute'" })); const validatedSecond = yield* pipe(zoneSecond, MInputError.assertInRange({ min: 0, max: 59, minIncluded: true, maxIncluded: true, offset: 0, name: "'zoneSecond'" })); return ZoneOffsetParts._make({ zoneHour: validatedHour, zoneMinute: validatedMinute, zoneSecond: validatedSecond }); }); /** * Returns the `zoneHour` property of `self` * * @category Destructors */ ZoneOffsetParts.zoneHour = /*#__PURE__*/Struct.get('zoneHour'); /** * Returns the `zoneMinute` property of `self` * * @category Destructors */ ZoneOffsetParts.zoneMinute = /*#__PURE__*/Struct.get('zoneMinute'); /** * Returns the `zoneSecond` property of `self` * * @category Destructors */ ZoneOffsetParts.zoneSecond = /*#__PURE__*/Struct.get('zoneSecond'); const _formatter = /*#__PURE__*/flow(/*#__PURE__*/CVTemplate.toFormatter(/*#__PURE__*/CVTemplate.make(/*#__PURE__*/_fixedLengthToReal({ ..._params, name: 'zoneHour', length: 3, numberBase10Format: /*#__PURE__*/pipe(_integer, CVNumberBase10Format.withSignDisplay) }), _sep.colon, /*#__PURE__*/_fixedLengthToReal({ ..._params, name: 'zoneMinute', length: 2 }))), /*#__PURE__*/Either.getOrThrowWith(Function.identity)); /** * Returns the ISO representation of this Gregorian Date * * @category Destructors */ ZoneOffsetParts.getIsoString = _formatter; /** * Returns the value of `self` expressed in hours * * @category Destructors */ ZoneOffsetParts.toHour = self => { const hour = self.zoneHour; const sign = MNumber.sign2(hour); return hour + sign * (self.zoneMinute / 60 + self.zoneSecond / 3600); }; })(ZoneOffsetParts || (ZoneOffsetParts = {})); /** * Type guard * * @category Guards */ export const has = u => Predicate.hasProperty(u, _TypeId); /** * Equivalence * * @category Equivalences */ export const equivalence = (self, that) => self.timestamp === that.timestamp; /** Proto */ const _TypeIdHash = /*#__PURE__*/Hash.hash(_TypeId); const proto = { [_TypeId]: _TypeId, [Equal.symbol](that) { return has(that) && equivalence(this, that); }, [Hash.symbol]() { return pipe(this.timestamp, Hash.hash, Hash.combine(_TypeIdHash), Hash.cached(this)); }, [MInspectable.IdSymbol]() { return getIsoString(this); }, ... /*#__PURE__*/MInspectable.BaseProto(moduleTag), ...MPipeable.BaseProto }; /** Constructor */ const _make = params => MTypes.objectFromDataAndProto(proto, params); /** * Returns the ISO representation of this DateTime * * @category Destructors */ export const getIsoString = self => GregorianDate.getIsoString(_gregorianDate(self)) + 'T' + Time.getIsoString(_time(self)) + ZoneOffsetParts.getIsoString(_zoneOffsetParts(self)); const _uncalculated = { gregorianDate: /*#__PURE__*/Option.none(), isoDate: /*#__PURE__*/Option.none(), time: /*#__PURE__*/Option.none(), zoneOffsetParts: /*#__PURE__*/Option.none() }; /** * Constructor that creates a DateTime from a timestamp and a zoneOffset for which no calculations * have been carried out yet. The `_zonedTimestamp` field is automatically calculated. Does not * check any input parameters */ const _uncalculatedFromTimestamp = (timestamp, zoneOffset) => _make({ ..._uncalculated, timestamp, zoneOffset, _zonedTimestamp: timestamp + zoneOffset * HOUR_MS }); /** * Constructor that creates a DateTime from a zonedTimestamp and a zoneOffset for which no * calculations have been carried out yet. The `timestamp` field is automatically calculated. Does * not check any input parameters */ const _uncalculatedFromZonedTimestamp = (zonedTimestamp, zoneOffset) => _make({ ..._uncalculated, timestamp: zonedTimestamp - zoneOffset * HOUR_MS, zoneOffset, _zonedTimestamp: zonedTimestamp }); /** Instance of an uncalculated DateTime that represents 1/1/1970 00:00:00:000+0:00 */ const _uncalculatedOrigin = /*#__PURE__*/_uncalculatedFromTimestamp(0, 0); /** * Tries to build a `CVDateTime` from `timestamp`, the number of milliseconds since 1/1/1970 * 00:00:00:000+0:00, and `zoneOffset` which gives the offset between the local time and the UTC * time. Returns a `Right` if successful, a `Left` otherwise. * * `timestamp` must be greater than or equal to MIN_TIMESTAMP and less than or equal to * MAX_TIMESTAMP. * * If `zoneOffset` is omitted, the local time zone offset of the machine this code is running on is * used. * * `zoneOffset` can be expressed as as a number of hours. In this case, it must be strictly greater * to -13 and strictly less than 15. * * It can also be expressed as an object containing three components: * * - `zoneHour` which must be greater than or equal to -12 and less than or equal to 14. * - `zoneMinute` which must be greater than or equal to 0 and less than or equal to 59. * - `zoneSecond` which must be greater than or equal to 0 and less than or equal to 59. * * Note that zoneHour=-0, zoneMinute=10, zoneSecond=0 is different from zoneHour=0, zoneMinute=10, * zoneSecond=0. The first corresponds to the string 'GMT-00:10', a negative 10-minute offset, the * second one to the string 'GMT+00:10', a positive 10-minute offset. * * `timestamp`, `zoneHour`, `zoneMinute` and `zoneSecond` should be integers. `zoneOffset`, when * expressed as a number of hours, does not need to be an integer. * * @category Constructors */ export const fromTimestamp = (timestamp, zoneOffset) => pipe(_uncalculatedOrigin, _setTimestamp(timestamp), Either.flatMap(setZoneOffsetKeepTimestamp(zoneOffset))); /** * Same as `fromTimestamp` but returns directly a `CVDateTime` or throws if it cannot be built * * @category Constructors */ export const fromTimestampOrThrow = /*#__PURE__*/flow(fromTimestamp, /*#__PURE__*/Either.getOrThrowWith(Function.identity)); /** * Builds a `CVDateTime` using Date.now() as `timestamp`. `zoneOffset` is set to 0. * * @category Constructors */ export const now = () => _uncalculatedFromTimestamp(Date.now(), 0); /** * Tries to build a `CVDateTime` from the provided parts. Returns a `Right` if successful, a `Left` * otherwise. * * `year` must comprised in the range [MIN_FULL_YEAR, MAX_FULL_YEAR]. `ordinalDay` must be greater * than or equal to 1 and less than or equal to the number of days in the current year. `month` must * be greater than or equal to 1 (January) and less than or equal to 12 (December). `monthDay` must * be greater than or equal to 1 and less than or equal to the number of days in the current month. * * `isoYear` must be comprised in the range [MIN_FULL_YEAR, MAX_FULL_YEAR]. `isoWeek` must be * greater than or equal to 1 and less than or equal to the number of iso weeks in the current year. * `weekday` must be greater than or equal to 1 (monday) and less than or equal to 7 (sunday). * * If there is not sufficient information to determine the exact day of the year, i.e. none of the * three following tuples is fully determined [year, ordinalDay], [year, month, monthDay], [isoYear, * isoWeek, weekday], default values are determined in the following order (the first match stops * the process): * * - If `year` and `month` are set, `monthDay` is taken equal to 1. * - If `year` and `monthDay` are set, `month` is taken equal to 1. * - If `year` is set and both `month` and `monthDay` are undefined, the day is taken to be the first * one in the year. * - If `isoYear` and `isoWeek` are set, `weekday` is taken equal to 1. * - If `isoYear` and `weekday` are set, `isoWeek` is taken equal to 1. * - If `isoYear` is set and both `isoWeek` and `weekday` are undefined, the day is taken to be the * first one in the iso year. * - If both `year` and `isoYear` are undefined, an error is raised. * * `hour23` must be greater than or equal to 0 and less than or equal to 23. `hour11` must be * greater than or equal to 0 and less than or equal to 11. `meridiem` must be one of 0 (AM) or 12 * (PM). If there is not sufficient information to determine the hour of the day, i.e. none of the * two following tuples is fully determined [hour23], [hour11, meridiem], default values are * determined as follows: * * - If `meridiem` is set, `hour11` is taken equal to 0. * - If `hour11` is set, `meridiem` is taken equal to 0. * - Otherwise, `meridiem` and `hour11` are taken equal to 0. * * `minute` must be greater than or equal to 0 and less than or equal to 59. If omitted, minute is * assumed to be 0. * * `second` must be greater than or equal to 0 and less than or equal to 59. If omitted, second is * assumed to be 0. * * `millisecond` must be greater than or equal to 0 and less than or equal to 999. If omitted, * millisecond is assumed to be 0. * * `zoneOffset` must be strictly greater to -13 and strictly less than 15. `zoneHour` must be * greater than or equal to -12 and less than or equal to 14. `zoneMinute` must be greater than or * equal to 0 and less than or equal to 59. `zoneSecond` must be greater than or equal to 0 and less * than or equal to 59. * * If there is not sufficient information to determine the exact time zone offset, i.e. none of the * two following tuples is fully determined [zoneOffset], [zoneHour, zoneMinute, zoneSecond], * default values are determined as follows : * * - If all parameters are undefined, the local time zone offset of the machine this code is running * on is used. * - If any of `zoneHour`, `zoneMinute`, `zoneSecond`, the undefined parameters are taken equal to 0. * * Note that zoneHour=-0, zoneMinute=10, zoneSecond=0 is different from zoneHour=0, zoneMinute=10, * zoneSecond=0. The first corresponds to the string 'GMT-00:10', a negative 10-minute offset, the * second one to the string 'GMT+00:10', a positive 10-minute offset. * * `year`, `ordinalDay`, `month`, `monthDay`, `isoYear`, `isoWeek`, `weekDay`, `hour23`, `hour11`, * `minute`, `second`, `millisecond`, `zoneHour`, `zoneMinute` and `zoneSecond` should be integers. * `zoneOffset` does not need to be an integer. * * All parameters must be coherent. For instance, `year=1970`, `month=1`, `monthDay=1`, `weekday=0` * `zoneHour=0`, `zoneMinute=0` and `zoneSecond=0` will trigger an error because 1/1/1970 * 00:00:00:000+0:00 is a thursday. `hour23=