Skip to content

Period

Canonical source: protos/lib/datetime/Period.protos at locked revision e99b712ecf658a527b1fe82c8beba2c1286d8dc9.

// THE LICENSED WORK IS PROVIDED UNDER THE TERMS OF THE ADAPTIVE PUBLIC LICENSE
// ("LICENSE") AS FIRST COMPLETED BY: Guillermo Adrián Molina. ANY USE, PUBLIC
// DISPLAY, PUBLIC PERFORMANCE, REPRODUCTION OR DISTRIBUTION OF, OR PREPARATION OF
// DERIVATIVE WORKS BASED ON, THE LICENSED WORK CONSTITUTES RECIPIENT'S ACCEPTANCE
// OF THIS LICENSE AND ITS TERMS, WHETHER OR NOT SUCH RECIPIENT READS THE TERMS OF
// THE LICENSE. "LICENSED WORK" AND "RECIPIENT" ARE DEFINED IN THE LICENSE. A COPY
// OF THE LICENSE IS LOCATED IN THE TEXT FILE ENTITLED "LICENSE.TXT" ACCOMPANYING
// THE CONTENTS OF THIS FILE. IF A COPY OF THE LICENSE DOES NOT ACCOMPANY THIS
// FILE, A COPY OF THE LICENSE MAY ALSO BE OBTAINED AT THE FOLLOWING WEB SITE:
// https://github.com/guillermomolina/protos
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
// the specific language governing rights and limitations under the License.
//! Calendar-relative amounts made of signed Integer `years`, `months`, and
//! `days`. A period is a frozen ordinary object with exactly those three
//! slots. The components are structural and are never normalized: twelve
//! months are not one year, and days are never converted to or from months,
//! because those relations depend on the date a period is applied to. A
//! period is not a fixed elapsed amount; elapsed amounts are
//! `std:datetime/Duration` values.
//!
//! Every period's immediate delegation parent is this module, the canonical
//! period factory and prototype. `recognizes` accepts exactly the periods of
//! this family: objects whose immediate parent is this module and whose local
//! slots are exactly the period state and behavior slots with Integer
//! components. Matching slots under another parent are not a period.
//!
//! Periods answer structural equality through `==`: two periods are equal when
//! both are periods and their years, their months, and their days are equal, so `(1, 0, 0)` and
//! `(0, 12, 0)` are different periods. `hash` is coherent with that equality,
//! so independently constructed equal periods work as the same Map key. `===`
//! remains ordinary object identity. Periods have no natural total order, and
//! this module deliberately provides no `compare`.
//!
//! Applying a period to a civil date is pure proleptic Gregorian arithmetic,
//! with no offset, zone, or clock authority:
//!
//! 1. `years` and `months` are combined into one month displacement and
//! applied to the date's year and month;
//! 2. the day of month is clamped once to the last day of the resulting month,
//! so 31 January plus one month is the last day of February;
//! 3. `days` are then added as whole calendar days.
//!
//! Years and months are never clamped separately, and application is not
//! repeated one-month steps: applying one month twice may differ from applying
//! two months once. Only the final date must lie in `std:datetime/Date`'s
//! supported years; a final date outside them signals an Error and is never
//! wrapped or saturated.
/// Answers a fresh frozen period of `years`, `months`, and `days`. Every
/// argument must be an Integer and may be positive, zero, or negative; any
/// other argument signals an Error. The components are stored as given.
call: (years, months, days) => {
Period: import("std:datetime/Period")
(Integer.recognizes(years) && Integer.recognizes(months) && Integer.recognizes(days)).ifFalse(() => {
Error().signal()
})
// Hashes are folded modulo a fixed prime so the accumulator stays bounded.
mix: (accumulated, value) => {
(accumulated * 31 + value) % 2305843009213693951
}
behavior: {
equals: (other) => {
period: this
Period.recognizes(other) &&
(period.years == other.years) &&
(period.months == other.months) &&
(period.days == other.days)
}
hash: () => {
period: this
mix(mix(mix(17, period.years.hash()), period.months.hash()), period.days.hash())
}
}
period: (Period) {
years: years
months: months
days: days
...behavior.alias("equals", "==")
}
period.freeze()
}
/// Answers `true` exactly when `value` is a period of this family: its
/// immediate delegation parent is this module, its local slots are exactly
/// `years`, `months`, `days`, `equals`, `==`, and `hash`, and every component
/// is an Integer. Answers `false` for any other value.
recognizes: (value) => {
Period: import("std:datetime/Period")
(value !== null) &&
(value.parent() === Period) &&
(value.slotNames().size() == 6) &&
value.hasSlot("years") &&
value.hasSlot("months") &&
value.hasSlot("days") &&
value.hasSlot("equals") &&
value.hasSlot("==") &&
value.hasSlot("hash") &&
Integer.recognizes(value.years) &&
Integer.recognizes(value.months) &&
Integer.recognizes(value.days)
}
/// Answers a fresh period with exactly the negated years, months, and days of
/// `period`, without any other normalization. A non-period argument signals
/// an Error.
negate: (period) => {
Period: import("std:datetime/Period")
Period.recognizes(period).ifFalse(() => {
Error().signal()
})
Period(period.years.negated(), period.months.negated(), period.days.negated())
}
/// Answers the `std:datetime/Date` reached by applying `period` to `date`:
/// the combined year and month displacement first, then one clamp of the day
/// to the last day of the resulting month, then the day displacement. A final
/// date outside the supported years, or an argument that is not a period or
/// a date, signals an Error.
addToDate: (period, date) => {
Period: import("std:datetime/Period")
Date: import("std:datetime/Date")
(Period.recognizes(period) && Date.recognizes(date)).ifFalse(() => {
Error().signal()
})
// Integer `div` truncates toward zero; calendar indices need the floor for
// a positive divisor.
floorDiv: (dividend, divisor) => {
quotient: dividend.div(divisor)
((dividend - quotient * divisor) < 0).ifTrue(() => {
quotient = quotient - 1
})
quotient
}
// Months are counted from month 1 of year 0. The intermediate year may lie
// outside Date's supported years when the day displacement brings the
// final date back inside them, so the calendar facts below are computed
// here for any Integer year rather than through Date construction. They
// use the same proleptic Gregorian rule as `std:datetime/Date`.
monthIndex: (date.year + period.years) * 12 + (date.month - 1) + period.months
year: floorDiv(monthIndex, 12)
month: monthIndex - year * 12 + 1
leap: ((year % 4) == 0) && (((year % 100) != 0) || ((year % 400) == 0))
length: 31
((month == 4) || (month == 6) || (month == 9) || (month == 11)).ifTrue(() => {
length = 30
})
(month == 2).ifTrue(() => {
length = 28
leap.ifTrue(() => {
length = 29
})
})
// The single clamp of the ratified application order.
day: date.day
(day > length).ifTrue(() => {
day = length
})
// Days are counted in 400-year eras of 146097 days, with years starting on
// 1 March so that the leap day ends each year.
shiftedYear: year
(month <= 2).ifTrue(() => {
shiftedYear = year - 1
})
shiftedMonth: month - 3
(month <= 2).ifTrue(() => {
shiftedMonth = month + 9
})
era: floorDiv(shiftedYear, 400)
yearOfEra: shiftedYear - era * 400
dayOfYear: (153 * shiftedMonth + 2).div(5) + day - 1
dayOfEra: yearOfEra * 365 + yearOfEra.div(4) - yearOfEra.div(100) + dayOfYear
dayNumber: era * 146097 + dayOfEra + period.days
resultEra: floorDiv(dayNumber, 146097)
resultDayOfEra: dayNumber - resultEra * 146097
resultYearOfEra: (resultDayOfEra -
resultDayOfEra.div(1460) +
resultDayOfEra.div(36524) -
resultDayOfEra.div(146096)).div(365)
resultDayOfYear: resultDayOfEra - (365 * resultYearOfEra + resultYearOfEra.div(4) - resultYearOfEra.div(100))
resultShiftedMonth: (5 * resultDayOfYear + 2).div(153)
resultDay: resultDayOfYear - (153 * resultShiftedMonth + 2).div(5) + 1
resultMonth: resultShiftedMonth + 3
(resultShiftedMonth >= 10).ifTrue(() => {
resultMonth = resultShiftedMonth - 9
})
resultYear: resultYearOfEra + resultEra * 400
(resultMonth <= 2).ifTrue(() => {
resultYear = resultYear + 1
})
// Date construction performs the final range check.
Date(resultYear, resultMonth, resultDay)
}
/// Answers the `std:datetime/Date` reached by applying the negation of
/// `period` to `date`; exactly `addToDate(negate(period), date)`.
subtractFromDate: (period, date) => {
Period: import("std:datetime/Period")
Period.addToDate(Period.negate(period), date)
}
/// Answers a fresh `std:datetime/LocalDateTime` whose date is
/// `addToDate(period, dateTime.date)` and whose time is the same
/// `dateTime.time` object, unchanged. Arguments that are not a period and a
/// local date-time, or a final date outside the supported years, signal an
/// Error.
addToLocalDateTime: (period, dateTime) => {
Period: import("std:datetime/Period")
LocalDateTime: import("std:datetime/LocalDateTime")
LocalDateTime.recognizes(dateTime).ifFalse(() => {
Error().signal()
})
LocalDateTime(Period.addToDate(period, dateTime.date), dateTime.time)
}
/// Answers the local date-time reached by applying the negation of `period`
/// to `dateTime`; exactly `addToLocalDateTime(negate(period), dateTime)`.
subtractFromLocalDateTime: (period, dateTime) => {
Period: import("std:datetime/Period")
Period.addToLocalDateTime(Period.negate(period), dateTime)
}

← datetime family · Standard-library source