Any miscellaneous information about the project or its modules can be found here.
-
In this section, we use the term
class
, a concept which strictly speaking JS doesn't support. However, we use JS patterns that emulate class-based code fairly closely; in particular, that emulate many of the features of the ES6class
syntactic sugar. This is currently done via the utilities provided in moduleclasses
. -
Objects that can be instantiated with these constructors are mutable, but few if any mutation methods are provided. This is because mutation makes code hard to reason about (it removes referential transparency). Instead of mutation, prefer to create a new object containing the new value, rather than mutating an old object.
PDP datasets use a variety of different, mutually incompatible calendar systems. These systems include:
- Standard or Gregorian calendar.
- 365-day calendar: Like the Gregorian calendar, but without leap years.
- 360-day calendar: Every month in every year has exactly 30 days.
JavaScript directly supports only the Gregorian calendar, via the Date
object. It is not possible (whilst retaining developer sanity, or code maintainability) to handle non-Gregorian calendars using a Gregorian calendar. Previous code that attempted to do so contained errors traceable to the incompatibility of different calendar systems.
To address this situation, we have defined a module calendars
containing the following items:
- Class
Calendar
, which represents the general notion of a calendar, and subclassesGregorianCalendar
,Fixed365DayCalendar
,Fixed360DayCalendar
, which represent specific, different calendar types.- Rightly or wrongly,
Calendar
s are used as instances (so far we have only discussed things that could be equally well be supplied by a fixed object, or singleton). - Each
Calendar
instance has an epoch year, which defines the epoch or origin date for computations the calendar can perform. Dates before Jan 1 of the epoch year are not valid. This is stupid, a result of lazy implementation, but it is true for now. Default epoch year is 1800. Calendar
has abstract methodsisLeapYear()
,daysPerMonth()
,daysPerYear()
that concrete subclasses define in order to specify different particular calendars.Calendar
provides a number of service methods for validating datetimes and for computing essential quantities, such as the number of milliseconds since epoch. These are fundamental to datetime computations within any given calendar system.
- Rightly or wrongly,
Most users of this module will not need to define their own Calendar
subclasses, nor their own instances of those subclasses (specifying epochYear
), since the provided standard instances are designed to meet known use cases in PDP. However, the option is there for unforeseen applications.
- The standard (and default)
epochYear
is 1800. - The
calendars
module offers pre-instantiated standard calendars of each type, indexed by the standard CF identifiers for each type:calendars['standard']
,calendars['gregorian']
calendars['365_day']
,calendars['noleap']
calendars['360_day']
The following classes exploit Calendar
objects to represent datetimes in specific calendrical systems.
-
Class
SimpleDatetime
that bundles together theyear
,month
,day
, etc. components of a datetime, without reference to any specific calendar. -
Class
CalendarDatetime
composes aCalendar
with aSimpleDatetime
, to represent a datetime in a particular calendar. (Note: We prefer composition over inheritance.)- At the moment, it offers only conversion methods (e.g.,
toISOString()
) and factories (e.g.,fromMsSinceEpoch()
). - This would be the class in which to place calendar-aware datetime arithmetic methods (e.g.,
addDays()
), but we have no use for this in present applications so the class lacks such methods.
- At the moment, it offers only conversion methods (e.g.,
In CF standards compliant datasets, datetimes are represented by index values (values of the time dimension) in a time system defined by units, start datetime, and calendar.
- Units are fixed intervals of time labelled by terms such as 'day', 'hour', 'minute'.
- A start datetime is a specification of year, month, day, etc., in a specified calendar system.
- The calendar is specified an identifier chosen from a fixed CF vocabulary that includes 'standard', 'gregorian', '365_day', 'noleap', and '360_day', with the obvious meanings.
- A time index t specifies a time point defined as t time units after the start datetime, in the specified calendar.
The following classes represent time systems and datetimes within such a system:
-
Class
CfTimeSystem
, which represents a CF time system, as above.- Constructed with arguments
units
andstartDate
; the latter is aCalendarDatetime
, which carries both the calendar and the datetime. This is one of the places where method signature is hard to remember, and could perhaps be improved.
- Constructed with arguments
-
Class
CfDatetime
, which composes aCfTimeSystem
and a real-valued index to represent a specific time within a CF time system.- Like
CalendarDatetime
(to which it is a parallel),CfDatetime
offers only conversion methods (e.g.,toISOString()
,toCalendarDatetime
) and factories (e.g.,fromLooseFormat()
). - Like
CalendarDatetime
, this is the class in which time arithmetic methods would be placed, but none are currently needed, so none exist.
- Like
Playing at classes is all very well, till somebody loses their mind. How is this intended to be used?
Here are some code snippets that show the application of these objects. Some make it obvious that it would be nicer to have (a) more consistent and/or flexible method signatures, and (b) more helper methods.
// Date in Gregorian calendar.
const apollo11 = new CalendarDatetime(new GregorianCalendar(), 1969, 7, 20);
console.log(apollo11.toISOString()); // -> 1969-07-20T00:00:00
console.log(apollo11.toISOString(true)); // -> 1969-07-20
// Or, using the pre-instantiated Gregorian calendar.
const apollo11 = new CalendarDatetime(calendars.gregorian, 1969, 7, 20);
// Dates in non-Gregorian calendar
const endOfCentury = new CalendarDatetime(calendars['365_day'], 2099, 12, 31);
console.log(endOfCentury.toLooseString()); // -> 2099/12/31 00:00:00
console.log(endOfCentury.toLooseString(true)); // -> 2099/12/31
// A CF time system: days since 1950-01-01 in 360-day calendar; maximum time index 99999.
const sinceDate = new CalendarDatetime(calendars['360_day'], 1950, 1, 1);
const cfTimeSystem = new CfTimeSystem('days', sinceDate, 99999);
// A CF datetime in the above time system, specified several different ways.
const cfDatetime = new CfDatetime(cfTimeSystem, 720);
const cfDatetime = CfDatetime.fromDatetime(cfTimeSystem, 1952, 1, 1);
const cfDatetime = CfDatetime.fromLooseFormat(cfTimeSystem, '1952/01/01');
console.log(cfDatetime.index); // -> 720
console.log(cfDatetime.toLooseString(true)); // -> 1952/01/01
// Some possibly useful time points in the above time system.
// Note these are `CfDatetime`s, not CalendarDatetime`s.
const start = cfTimeSystem.firstCfDatetime();
console.log(start.index); // -> 0
const end = cfTimeSystem.lastCfDatetime();
console.log(end.index); // -> 99999
const today = cfTimeSystem.todayAsCfDatetime();
console.log(today.index); // -> some value around (<current year> - 1950) * 360