Day home Documentation
Day Specification

Architecture

DAYo object

Calendar dayYear, month and day or year and month or just a year
Not knownWe simply don't know. (Not the same thing as Not valid)
Not validWe've found some logical error. (Not the same thing as Not known)
Beginning of timeWe don't have data going back beyond a certain point
End of timeOngoing. A very common pattern in real-world applications
FloatingA month or a month and a day. Start of tax years for example
IntervalNumeric values of days, months and years used when comparing or shifting days
Not valid intervalSomething went wrong with calculations

DAYo object storage and inter-working with other date and data systems

See the Data representation documentation.

Intervals

Defining methods

1Difference is not the same thing as duration. if d1 is the same as d2 then the difference is zero days but the duration is one day. This illustrates the richness of what we can do if we think about dates properly but also the pitfalls for the unwary.

System architecture

Definitions

Abstract date
Abstract meaningsThe exact meanings of these values depends on the application. The systems developer has to know what these are.
Beginning of time, End of time or Not known. Day has well defined rules for how to process these in various contexts.
Calendar date This is 'a date that might appear on a calendar'.
  • Just a year
  • Just a year and month
  • A year month and day
Floating date This applies to days with unspecified 'more significant' components. ie.
  • Only month and day components specified. eg. Birthday
  • Only day component specified. eg. Monthly pay day
Fully specified date A calendar date where the three elements, year, month and day are defined and real. If it is 'one square' of the calendar on your wall then it's fully specified.
Interval A DAYi object may be used to explicitly represent a time period expressed as ±,Y,M,D.
  • The sign applies to the whole.
  • Intervals should not be used to represent dates.
Julian date The number of days elapsed since January 1st 4713 BC. For our purposes a serial number for days which always 'counts one' every 24 hours. This only applies to fully specified dates, but where this holds we can do exact date arithmetic.2.
Period
WarningDo not get the terminology of Periods mixed up with Intervals. An Interval is a particular type of Day object. A Period is a lack of Precision.
A date never specifies a moment in time. The least span of time it covers is 24 hours. The Period of a date is the full range of possible dates that it might cover. For example "May 2007" covers a Period from "1st May 2007" to "31st May 2007" inclusive.
Precision Analogous to numerical precision when dates are specified as Y-M-D with Y being the most and D being least significant. Part of the power of Day is being able to deal with imprecise values.
Signature A top-level indicator of the type of value contained in a day encoding.
2WARNING: Some people misuse Julian Date to mean the "ordinal date" which is the number of the day in the year.

Specification as implemented in Javascript
We're trying to do two similar but different things here. Firstly provide a generic implementation-agnostic specification, and secondly to show a reference implementation that covers all the bases. This means some things will need fudging. For example Javascript is weakly typed but we might suggest an unsigned 32-bit integer is returned. As well as this specification there are hundreds of tests where we attend to minute details and edge-cases which are too pernikerty to document here.
See also A programmer's API quick reference is available here We suggest having quick scan to start with.
However it's really important that the concepts associated with the manipulation of abstract values and not-quite straightforward methods are understood, which is the purpose of this document.
Grey area between generic API and Javascript implementation This document is a how things work document which, although it uses the Javascript implementation for illustration simplifies certain details of the Javascript implementation so that the underlying methods are clear.
Although it's pretty close, this is not meant to be a definitive Javascript API reference.
Inside the reference system there are various helper classes, however for the sake of simplicity in this reference documentation we'll have a single DAY class which has class methods and DAY instances with methods. 𝔻 is used to indicate a DAY instance. So for example 𝔻.DayOfWeek() gives the day of the week of an instance, while DAY.version is a class property.

Global Day properties

These are class properties, ie. called directly on the DAY class.

Miscellaneous

DAY.versionString Telling the code build date and time. Version numbers are not used.
DAY.iNVInteger Occasionally a function that returns an integer will want to return an alarm rather than throw an exception. Js value happens to be -9999999

Signatures

Return an integer 0 to 7.
DAY.NVI0 Invalid interval
DAY.INT1 Interval
DAY.NV2 Not a date value
DAY.FLO3 Floating date
DAY.NK4 Date not known
DAY.BoT5 Beginning of time Or such an interpretation as 'before we started counting' or 'some time ago'
DAY.Cal6 Calendar day
DAY.EoT7 End of time Or such an interpretation as 'not yet'

Unix timestamp representation

DAY.UNIX_NVInteger 13 Dec 1901 02:00:00 This value is without a reason code. (Which would be in minutes) -2147551200
DAY.UNIX_NKInteger 13 Dec 1901 04:00:00 -2147544000
DAY.UNIX_BOTInteger 13 Dec 1901 05:00:00 -2147540400
DAY.UNIX_EOTInteger 18 Jan 2038 07:00:00 2147410800

Error handling

We understand two sort of errors that can appear:
  1. A programmer has allowed something incorrect to happen or the system is confused beyond it's ability to handle the strangeness. For example if a function needs an integer and receives a string then that's a programming error. These (mostly) throw exceptions.
    An exception is shown like this.
  2. Then there's the inevitable issues with unsuitable inputs. For example what should happen if we try to find the latest date of two days where one is NV? These return values which can be interpreted by further processing without triggering a meltdown.
We try to be flexible when it comes to inputs and definite when it comes to illogical combinations of methods and data values.

Environment

Obviously day and month names are different in different languages. So are other conventions and error messages that a user may see also. An appendix will normally be loaded before any DAY operations. This can be used to pre-set system-wide parameters.

Appendix files

The naming and formatting of appendix files is dealt with in more detail here This also describes algorithm configuration settings.

Environment properties

DAY.appendixstring The actual file name being used. If no separate appendix found and using internal defaults then this will be en-gb.dax
DAY.errorstring Examine this if there's a problem loading an appendix. A null string indicates no problem found
DAY.force2or4DigitYearsboolean If true then years must be in the form yy or yyyy @@@ Check actual implementation
DAY.i18nstring relative path to directory where the appendices are located. @@@No trailing slash (to be allowed)
DAY.intervalRegExstring Regular expression that detects intervals as input Hack this if you don't want to use y, m and d
DAY.yearLimitinteger Maximum allowable size of year + or -
To avoid duplication there are some other important properties described in the internationalisation reference above.

Environment methods

Boolean get/set methods only set if the optional argument is actual boolean true or boolean false.
DAY.AllowDayOnlyIntervals(optional boolean new value) Get/Set do we allow INTs to be created with more than 31 days eg +0y 0m 66d If not then the conversion factor 30.4375 (365.25/12) will be used.
DAY.AllowExcessDaysInMonth(optional boolean new value) Get/Set true if we allow silent coercing of slightly too many days in month to end of month For example is 31st September assumed to be the last day of September? If this is false then an input error will be raised.
DAY.BCIndicator(optional string new value) Get/set the string used for BC indication. Do not call with a null string unless you want the BC indicator to be eliminated! The get version would be called called as BCIndicator().
DAY.ImpliedYear(optional integer) Get/Set numeric value of IMPLIED_YEAR Allowable inputs are 0,-1,-2 or a year > 999
DAY.InYearLimits(Integer year to test),
↩ boolean
Check to ensure the year given is within current limits. Only applies to INT and Cal.
DAY.Initialise There will always be an Initialise method which will set up the environment according to (a) some file or (b) some text. The JS implementation leaves getting a file (and any associated async issues) to the programmer and takes the text.
DAY.IsDmyOrderBoolean Get/set whether element order is D-M-Y (False if M-D-Y)
DAY.Lookup(string Key, integer number)
↩ string
This is a low-level method to access the dictionary
DAY.MonthName(integer Number, integer Version)
↩ string
Look up the month name for the given number. Version is 1 to 4 depending on the length required.
DAY.SearchMonths(string Input)
↩ integer
Use the string supplied to see if it will match with any month names. If so then return 1 for Jnuary and so on. Return 0 for no match. This only matches when there is exactly one match even if that match is partial. For example J would fail as not being specific but Janua would succeed as although it is partial it can only refer to one month.
DAY.SearchShortcuts(string Input)
↩string
See if input matches one of the shortcut formats. For example be would match with Beginning-of-time and would return BoT. If there's no match then return a null string.
DAY.SetKeyValue(string Key, string Value) This is a low-level method of setting some element of the environment. It is not persistent over sessions.
DAY.TwoDigitFix(optional number) Get/set TWO_DIGIT_FIX Allowed values are 0, 20 and 50

Constructor

Typically unsuitable inputs will not throw exceptions but instead return a 𝔻 with the NV Not Valid signature. When this happens there will be coarse error code which is persistent. (Appendix section 7) and a 'what went wrong string' (Accessed using 𝔻.GetWWWS()) which is not persistent. The style of these strings is [method name] explanation. In this documentation we'll flag the situations where this might happen [like] this.

As well as the master constructor DAY(Initialiser,Instructions) there are some quickie constructors and a number of helper methods which deal with the awful details. These constructor-helpers are named DAY._Read... For example to create a 𝔻 from a Julian number you can call the constructor with var d = DAY(someJulianNumber,'J'); which will use ._ReadJulian() to do its dirty-work. If you want to reuse an existing 𝔻 object then you can call these methods directly, for example. d._FromArray(someArray); // warning no sanitizing

Quickie constructors

DAYu.MakeBOT () Construct a 𝔻 for Beginning-of-time
DAYu.MakeClone (𝔻) This 𝔻 takes all the data from the argument What went wrong is blanked.
DAYu.MakeEOT () Construct a 𝔻 for End-of-time
DAYu.MakeNK () Construct a 𝔻 for Not-known
DAYu.MakeNV (number Code, optional string Description, optional 𝔻 Data) Construct a 𝔻 for Not-valid
  • Code is the numeric value 0..7
  • Description is an optional 'What Went Wrong String' This is of the form [Name of routine ] Description
  • Data is an optional 𝔻 which may contain interesting Y, M or D state to help diagnosing problems.
DAYu.MakeToday () Construct a 𝔻 for today's date

Main constructor

Creates a DAYo object with Intitaliser as the data and an optional Instructions to tweak or command. In all cases the triksy grunt work is done by methods with the name convention ._Read...()

Constructor helpers

Warning These are not constructors or class methods.
𝔻._Read32Bits
boolean
(Unsigned integer) See Data representation for details of 32-bit encoding. The resulting 𝔻 is faithful to the input. There is no validation in this routine but the master constructor will perform ._Sanitize(). If you want a guarenteed representation of the actual data bits then use the following instead of the master constructor.
var d = new DAYo(); d._Read32Bits(someUnsignedInt);
𝔻._ReadArray
boolean
(Array) The array is numerical of the form [Signature,Sign,Year,Month,Day,Error code] The Sign may be DAY.pos or DAY.neg, however any negative value for Year will take precidence over the Sign value. The resulting 𝔻 is faithful to the input, except the Year is non-negative. There is no validation in this routine.
𝔻._ReadDate
boolean
(Javascript Date object, optional boolean UseTimeBits) Use a Javascript Date instance.
Consider the simple case where you have an everyday Javascript Date you want to convert to a 𝔻. Simply write myDay = DAY.FromDate(myJsDate) Since Javascript dates all have actual day, month, year parts the result will be fully specified day representing a single calendar day. (The H:M:S parts will be lost.) So far so normal and simple, but there are edge cases where the year is 0 (Eeek! There is no year zero in the calendar as it goes straight from 1BC to 1AD.) or has a magnitude more than 4095.

For soft-hearted reasons of indulgence we allow Javascript dates to use the H:M:S elements for vital storage. (Not recommended of course, but if you have to interface with a lagacy system then this may be an option.) The gnarly details of the encoding are here To force reading these H:M:S elements set UseTimeBits to true.

𝔻._ReadJulian
boolean
(number) Use a Julian day number. This will be a fully specified calendar date. There is no validation here.
𝔻._ReadString
boolean
(Inout string, Templates array of strings)
  • This is the preferred method for reading everyday strings into a 𝔻 (By all means try DAYo._ReadText() and hope for the best if you're expecting a plethora of inputs, or throw ._ReadText() into your early code with a note to tighten things up later with ._ReadString().
  • The key difference between ._ReadString() and ._ReadText() is that the latter does more guessing about which element is which. So you might get unexpected '5th of the 10th' when you should have '10th of the 5th'.
  • ._ReadString is more competent to deal with missing years and completely missing inputs. For example it can be told what to infer from a blank input. Say on the database of authors you have born and died fields. For a living author you could allow blanks to be represented as Not Known. It will also guess missing years if you want. For example 10 Feb will be (if allowed by template) be interpreted as 10th Feb last year. We draw your attention to the dangers inherent but for inputs that are around the current date this saves a lot of clerical monotony.
  • The templates array is strings with simple place-marker codes. These are tested against the input until a match is found. For example ["TY","D2M2Y4","D1_MN_yy","nk"]
    Tokens to combine
    Y2Two digit year
    Y4Four digit year
    YYTwo or four digit year
    yyAs YY but guess if missing
    M1One or two digit month number
    M2Two digit month number
    MNMonth name. Including variants
    D1One or two digit day number
    D2Two digit day number
    aaJunk alpha-numerics to be ignored
    Separators
    spaceOptional separators
    underscoreOne or more mandatory separators
    Solo tokens
    TYToday shortcut
    NVNot valid shortcut
    NKNot known shortcut
    nkBlank interpreted as not known
    ETEnd of time shortcut
    etBlank interpreted as end of time
    BTEnd of time shortcut
    btBlank interpreted as beginning of time
    DEBUGSwitches on diagnostics which
    can be accessed by .GetWWWS()
  • The space separator in the templates indicates zero or more separators. So for example pattern D1 MN Y4 would match inputs of 14May2017 as well as 14 May 2017.
  • This never guesses missing day and month order or values. It only guesses the year if the template token yy is used. Then the logic is to assume that the inputs are for the very common use case of dates about now. Think of diary appointments, event logging etcetera. So we let missing-year inputs being created in the first few months of the year with an input date of the later months guess the previous year. For example, suppose it's now January and I'm typing in 25 Dec: What should the missing year be? Almost certainly last year. There is a numeric parameter DAYu.YEAR_GUESS_DELAY which tells the number of months in the past for which this guessing should be applied. Of course this guessing is not always appropriate but we're sure it can be applied in many cases.
  • Year guessing for Y2 and YY (not yy which is always current or just-previous) uses the same rules as ._ReadText() controlled by the DAYc.TwoDigitFix
  • If the BCIndicator is found in the input it will automatically be registered
  • See the API documentation for the various exceptions that can be thrown. These are all concerned with badly formed template patterns.
  • A test page here is provided so that template pattern and input data combinations can be tested.
𝔻._ReadText
boolean
(string String) Try to interpret the text input. This is quite involved as it requires conversion from userland terms to codeland, where they then may be ambigious or partial and need configurable rules to arrive at a result. Sections 5 and 8 of an appendix tweak the interpretation. Sections 1 and 3 and INPUT_HACKS in section 5 govern names of months. Day names are never used for input.

PatternNotes
Signatures for NV,NK,BoT and EoT
  • As defined in appendix section 1.
  • These are processed first and exclusively
Last token is BC As defined in BC_INDICATOR
Month name found anywhere?
  • All other tokens must be numeric. [_ReadText] Multiple alphas
  • The names (can be partial if unique) are defined in section 3.
  • An alpha token must resolve to a known month. [_ReadText]Unknown month name
  • From a UI point of view a month name is good news as it eliminates all day/month ambiguity.
Token is zero [_ReadText] Zero token is not valid
Token is neither all digits or all alpha [_ReadText] Unexpected alpha
Only one token
1..4 digits Year
  • See DAY.Convert2to4DigitYear() which may result in [_ReadText]2 to 4 digit conversion failed
6 or 8 digits ddmmyy or mmddyy or ddmmyyyy or mmddyyyy
Any other number of digits [_ReadText] Strange numerical token
Two tokens
alpha number
  • Must be month - year or month - day
  • Lots of fiddling via appendix settings
number alpha Must be day - month
number number
  • Either day - month or month - day...
  • But things like 4 2016 could be interpreted as 'April 2016'
  • But perhaps we don't allow missing years... Or perhaps we do.
  • Because of the complex interaction of edge cases there's a developer's test page where the appendix settings can be tweaked and results checked. The danger is letting DAY make false assumptions while trying to be helpful and reduce finger strain for clerks.
Three tokens
m/d d/m y
  • The final token must be year...
  • Except if DAY.SMALL_YEARS_TO_DAYS is set to 1
  • Then pretty much the same as for two tokens. (The same warning applies.)
This is probably the most gnarly routine in Day. It tries to be super-flexible in itself, taking guidance from appendix settings. It's a pretty good idea to check 𝔻.IsErrorFree() straight afterwards. Remember you have the error code to userland translations in section 7 of the appendix, while 𝔻.GetWWWS() is a more detailed codeland message.
𝔻._ReadTilde
boolean
(string tilde-representation) Use our simple tilde format. (See Data Representation)
[_ReadTilde] Unable to read tilde format"
𝔻._ReadTimestamp
boolean
(integer unixMilliseconds, optional boolean UseTimeBits)
  • Normally the time part is irrelevant and ignored
  • If you're using the H:M:S part of a timestamp for any reason, (Why?) then set the second argument to true.
𝔻._ReadYMDHMS
boolean
(string, optional boolean UseTimeBits) Use a string formatted as YYYY-MM-DD with optional HH:MM:SS addition.
  • Typically this will return a fully specified date, but the time elements may have been hacked to shoehorn a full DAYo object's data into an existing database field etc. Time elements are ignored unless optional {UseTimeBits} is true.
  • [_ReadYMDHMS] expected YMD part missing"
  • [_ReadYMDHMS] expected HMS part missing applies only if UseTimeBits is true.

Object methods (read)

There should never be the need to access the internal properties of a 𝔻 object directly. Appropriate getter methods are provided.
𝔻.AddDays
𝔻
(number) add (-ve for subtract) a given number of days from a specific CAL date.
  • NK,EOT,BOT return themselves
  • Anything else returns NV
  • When adding days alone to a SPECIFIC date count according to the real calendar.
  • [AddDays] Subject couldn't be converted to Julian when a inapropriate 𝔻 is used.
  • Otherwise it gets messy...
𝔻.AddMonths
𝔻
(number) Add ( -ve for subtract ) specified number of months.
  • When adding months alone to a date: Convert the days part of the date into fractions of that month. Then add the months, carrying base 12 as required. Now convert the fraction back into days according to the actual length of the new month.
  • May throw error if trying to add months to just a year but this will be ignored if the number of months to add/subtract is a multiple of 12
𝔻.AddYears
𝔻
() Calls 𝔻.AddMonths()
DAYu.After
boolean
(𝔻 testDay) Compares the logical values of the dates. Returns true if THIS is DEFINITELY after testDay argument. Note there are many cases where this test doesn't make sense or where the data cannot possibly be interpreted. All of these will return false without any error being raised. Therefore it is up to the programmer to avoid propagating spurious FALSEs by suitably guarding before calling.
DAYu.AsDays
floating point number
() Convert an interval into days. Result may be fractional and negative
DAYu.Before
boolean
(𝔻 testDay) Compares the logical values of the dates. Returns true if THIS is DEFINITELY before testDay argument. Note there are many cases where this test doesn't make sense or where the data cannot possibly be interpreted. All of these will return false without any error being raised. Therefore it is up to the programmer to avoid propagating spurious FALSEs by suitably guarding before calling.
𝔻.CalendarType
integer
() If this is a CAL then what type is it?
  • fully specified DAY.CALTYPE_YMD
  • no day DAYu.CALTYPE_YMX
  • just year DAYu.CALTYPE_YXX
  • not calendar DAYu.CALTYPE_ERR
𝔻.DateToString
string
(string Template)
  • return a string representation of a CAL or FLO The format string is a sequence of two-character tokens that describe the elements to be included in the output These may be interleaved with literal characters.
  • The Day(number) and Month elements will normally automatically be exchanged to match the DM_ORDER parameter in the appendix. (Can be disabled with the -S flag)
  • Template place holders
    PlaceholderResult
    y1year without any leading zeroes
    y22-digit year
    y44-digit year
    m11 or 2-digit month number
    m22-digit month number
    M2Month name tiny
    M3Month name short
    M4Month name long
    d11 or 2-digit day number
    d22-digit day number
    D2Day name tiny
    D3Day name short
    D4Day name long
    BCBC indicator
  • Flags
    FlagMeaning
    -TDon't use Today
    -EAdd extended NV and NVI reasons
    -SDisable effect of DM_ORDER
  • Example template: Your birthday: d1 M4 -T
𝔻.Day
integer
() Returns the day value.
  • 0 implies not set
𝔻.DayOfWeek
integer
() Day of the week
  • No date-day returns 0
  • Monday → Sunday returns 1 → 7
𝔻.DumpStr
string
() Return string version of internal state for debugging.
𝔻.FirstDay
𝔻
() Returns the first possible date covered by this 𝔻
𝔻.FloatType
integer
() If this is a FLO then what type is it?
  • floating day DAY.FLOTYPE_XXD
  • floating month DAYu.FLOTYPE_XMX
  • floating month-day DAYu.FLOTYPE_XMD
  • not floating DAYu.FLOTYPE_ERR
𝔻.FractionOfMonth
floating point
() How far through month are we?
  • Might throw error if year needed for February and not given
  • Only applicable to CALs of form y+m+d and m+d
𝔻.GetErrorCode
integer
() What is numerical value of the error code?
  • 0 signifies none.
  • More details may be found using .GetWWWS()
  • See also .IsErrorFree()
  • To translate an error into userland text do something like
    var e = myDay.GetErrorCode(); if(e>0){ var estring = DAYc.Lookup('NVRC',e); ...
𝔻.HasDay
boolean
() Return true if day component is specified
𝔻.HasMonth
boolean
() Return true if month component is specified
𝔻.HasYear
boolean
() Return true if year component is specified
𝔻.IsBC
boolean
() Return true if the sign component is negative
𝔻.IsBoT
boolean
() Test for BoT signature
𝔻.IsCalendar
boolean
() Is this a CAL of the form Y or YM or YMD
𝔻.IsEoT
boolean
() Test for EoT signature
𝔻.IsErrorFree
boolean
() Return true if there is no error set
𝔻.IsFloating
boolean
() Test for FLO signature
𝔻.IsGiven
boolean
() This tests for a workable date. ie NK, BoT, CAL or EoT.
𝔻.IsGoodCalendar
boolean
(boolean MonthRequired, boolean DayRequired) Is it error-free and a calendar day with specified precision?
𝔻.IsKnown
boolean
() This tests for a known date. ie BoT, CAL or EoT.
𝔻.IsNotKnown
boolean
() Test for NK signature.
𝔻.IsRealPeriod
boolean
() Tests for a valid CAL which could be Y or YM. ie. Day component is missing
𝔻.IsSameValue
boolean
(𝔻 AnotherDay, boolean StrictFlag) Do the internal values (signature, sign, y, m, d) match?
  • If the StrictFlag is truthy then the error codes must also match.
𝔻.IsSpecific
boolean
() Is this a Cal that referrs to a specified day. ie. Day, month and year components are all specified
𝔻.IsToday
boolean
() Return true if this is today.
𝔻.IsValid
boolean
()??????????????????????????????
𝔻.IsValidDate
boolean
()?????????????????????????????
𝔻.Julian
integer
() Return Julian day number
  • Returns iNV if not a fully specified date.
𝔻.LastDay
𝔻
() Returns the last possible date covered by this 𝔻
𝔻.Middle
𝔻
() Returns the middle date covered by this 𝔻
𝔻.Month
integer
() Returns month value
  • 0 implies not set
  • To translate a month into userland text do something like
    var m = myDay.Month(); if(m>0){ var mstring = DAYc.Lookup('M'+m,1); // Full month name ...
𝔻.MonthsDifference
floating point
(𝔻) See class DAY.MonthsDifference()
𝔻.Next
𝔻
() Return a 𝔻 which represents the 'next' in sequence. This may find the next day, month or year depending on the precision of the 𝔻 being examined.
  • eg. 1957 → 1958
  • eg.Aug 1957 → Sep 1957
  • eg.13 Aug 1957 → 14 Aug 1957
  • Technically allow floats also eg 13 Aug -> 14 Aug but leap years will be a potential disaster so this IS NOT RECOMMENDED for use with floats.
  • DO NOT try to add 3 days with three calls to this function etc.
  • Throws exceptions in the case of an unsuitable signature.
𝔻.Previous
𝔻
() See .Next()
𝔻.Signature
integer
() Returns signature
𝔻.SortsBefore
boolean;
(𝔻) Does the subject 𝔻 sort before the argument?
  • Intervals sort by magnitude
  • All other representations sort by 32-bit representation
  • See also DAYu.SortsBefore which is more suitable for incorporating into a sorting routine.
𝔻.To32Bits
unsigned integer
()Returns an unsigned 32-bit number
bits 31 - 29 Signature
bits 28 Sign 0:-ve 1:+ve
bits 27 - 16 Year 0 ... 4095
bits 15 - 12 Month 1:Jan ... 12:Dec 0 is legal
bits 11 - 7 Day of month 1 ... 31 0 is legal
bits 6 - 3 Day of week (Fully specified) 1:Mon ... 7:Sun 0:Undefined
Not valid reason code (NV) See table 7
bit 3 Not used
bit 2 Valid flag 1:Is validated
bit 1 Fully specified 1:Is fully specified
bit 0 Interval 1:Is interval
𝔻.ToDate
Javascript Date
() See 𝔻.ToYmdhms()
𝔻.ToString
string
(optional string Template, optional boolean ExtendedErrorCode) General routine
  • If a template is provided see .DateToString() for details
  • Template may be ~ to return a tilde separated string, as per .ToTilde()
  • if ExtendErrorCodeFlag is missing or false then emit a numeric string. If true then use appendix to emit a user-land message.
𝔻.ToTilde
string
(optional boolean NumericFlag) Return easy to read string that is part of a standard
  • The format is sig~sign~y~m~d~error
  • Normally the output will be easy to read mnemonics and no padding but if NumericFlag is true then all values will be numeric and padded where appropriate. You'd use the NumericFlag if you wanted to do a sort on this column in a database say.
𝔻.ToYmdhms
varies
(string ResultType) Lossless encoding into a variety of types depending on ResultType.
  • ResultTypeReturns
    ARRAYAn array encoded the form [y,m,d,h,m,s]
    DATEA javascript Date object with h,m,s encoded
    YMDHMSA string formatted as yyyy-mm-dd hh:mm:ss
    YMDHMSZA string formatted as yyyy-mm-ddThh:mm:ss.000Z
    STAMPNumber. Unixy timestamp version (milliseconds since January 1, 1970, 00:00:00 UTC. Negative for prior)
  • Can throw exceptions ToYmdhms() has unknown ResultType where the programmer has supplied a daft argument, and ToYmdhms() is unable to make a date. where the javascript Date constructor fails.
𝔻.Year
signed integer
() Returns year value
  • 0 implies not set
𝔻.YearsDifference
floating point
(𝔻) See class method DAY.YearsDifference()

Utility methods

These are class methods namespaced here with DAYu which implement many of the behind-the-scenes functions and provide easy-to-use functions for general use.
DAYu.
array
(number dayCount) DayCount is a number of days (Not necessarily an integer) Return an array of [Y,M,D] after applying the necessary conversions
  • Uses the fractional conversion factor DAYu.DAYSINMONTH
DAYu.DayOfMonthToFraction
number
(number of days) How far through the month is this d/m/y?
  • Year needed for DaysInGivenMonth() if year is missing.
DAYu.DaysDifference
number
(𝔻 aDay1, 𝔻 aDay2) Number of days difference between aDay1 and aDay2.
  • Normally requires both dates to be fully specified
  • With the exception of both being FLO with a day set.
  • Unsuitable arguments will return iNV
DAYu.DaysInGivenMonth
integer
(integer Month, optional integer Year) How many days in given month?
  • If Month is 2 then use (optional) Year to tell us if it is a leap year
  • If Year is present and 0 then it is not a leap year.
  • Illegal m argument or m=2 and y=undefined throws an exception
DAYu.EarliestFinish
𝔻
(array of 𝔻s, optional boolean IgnoreUnsuitable) What is the earliest finish of these dates? Only CALs are suitable values for the array.
  • NVI,INT,NV,FLO will throw an exception
  • NK,BoT,EoT will NORMALLY throw an excepton... the optional 2nd arg is set true when NK,EoT,and BoT are ignored.
  • Zero-length array throws an exception
DAYu.EarliestStart
𝔻
(array of 𝔻s, optional boolean IgnoreUnsuitable) What is the earliest possible start of these dates? Only CALs are suitable values for the array.
  • See DAYu.EarliestFinish()
DAYu.FractionToDayOfMonth
integer
(number Year, number Month, number Fraction) Given a decimal between 0 and 1 what is the equivalent day.
  • Inverse of DAYu.DayOfMonthToFraction()
  • This is an important method when trying to do things with months. For example the end of January plus one month can be coded as follows:
    var frac = DAYu.DayOfMonthToFraction(2017,1,31); var febLastDay = DAYu.FractionToDayOfMonth(2017,2,frac);
    Note that the year is required when messing about with February.
DAYu.GetDefaultYear
integer
() return the default year.
  • Uses IMPLIED_YEAR in the appendix to tell us what to do. Return iNV if missing years are not allowed
  • IMPLIED_YEARReturnDescription
    0iNVDo not allow inputs that miss out year part.
    -1yyyyUse the current year
    -20Allow floating date input
    nnnnyyyyUse the specified year
  • See also DAYc.TwoDigitFix() method.
DAYu.IsLeapYear
boolean
(integer Year) Return true if the argument is a leap year.
DAYu.JulianToYMD
array
(integer JulianDay) Split Julian day number into array of [Y,M,D]
  • (JulianToYMD) expects a numeric
DAYu.LatestFinish
𝔻
(array of 𝔻s, optional boolean IgnoreUnsuitable) What is the latest possible finish of these dates? Only CALs are suitable values for the array.
  • See DAYu.EarliestFinish()
DAYu.LatestStart
𝔻
(array of 𝔻s, optional boolean IgnoreUnsuitable) What is the latest possible start of these dates? Only CALs are suitable values for the array.
  • See DAYu.EarliestFinish()
DAYu.MonthsDifference
number
(𝔻 Day1, 𝔻 Day2) calculate (or approximate if days are involved) the number of months difference.
  • Roughly Day2 - Day1
  • Arguments must have the same pattern of y/m/d components and be CAL or FLO. Allow patterns are:
    CAL y m d, y m 0 or y 0 0
    FLO 0 0 d, 0 m 0 or 0 m d
DAYu.SigStrToConst
integer
(string Signature) Convert a three character string into a signature constant
  • Case insensitive
  • Looks-up DAYu.SIGNATURE_STRINGS
  • Special case where digits 0..7 are allowed 'as they are'.
  • Returns constant DAYu.NV if unable to interpret string.
DAYu.SigToStr
string
(string Signature, optional boolean NumericFlag) Signature code to three character string
  • Signature is number 0..7
  • Return appropriate string from DAYu.SIGNATURE_STRINGS
  • If optional boolean NumericFlag is true then return a single digit string "0".."7"
DAYu.SignStrToConst
integer
(string Sign)
  • -or -ve return DAYu.neg, anything else returns DAYu.pos
DAYu.SignToStr
string
(number Sign, optional boolean NumericFlag) Sign to three character string.
  • Sign is 0 or 1 returning - or +
  • If optional boolean NumericFlag is true then return a single digit string 0 or 1
DAYu.SortsBefore
integer
(𝔻 Day1, 𝔻 Day2) Compare two DAYo objects. Return -1, 0 or +1;
  • If both are intervals then compare magnitude otherwise compare on 32 bit representation.
  • Ifreturn
    Day1 < Day2-1
    Day1 == Day20
    Day1 > Day2+1
  • (Note this is SIMILAR but not identical to DAYo.SortsBefore() which conflates == and > into false.)
DAYu.YMDToJulian
integer
(number Year, number Month, number Day) Create Julian day number from Year, month and day
  • Year can be positive or negative
  • Unsuitable inputs will throw exceptions.
DAYu.YearsDifference
number
(𝔻 Day1, 𝔻 Day2) 'Subtract' Day1 from Day2
  • Practically the same as DAYu.MonthsDifference() / 12 except unsuitable args returns iNV
  • Where CAL pattern is y 0 0 the calculation is simple subtraction.
DAYu.
()

Intervals

Confusion warning
Intervals are close cousins of DAYs but in this implementation we'll deal with them as completely separate things. Their purpose is separate but there are overlaps and interactions.
This implementation may change, but for now an interval is a DAYi object shown here as 𝕀.

Description

And also Elsewhere we've discussed tricksy date 'arithmetic' such as what's the 'difference' between '1 Jan 2017' and 'March' and what's 'Feb 28' plus 1 month?
An interval, shown here as 𝕀, describes an amount of time. Traditionally the real-world expression Some_Date minus Another_date has been converted into var interval = someTimestamp - anotherTimestamp; which might work for hours minutes and seconds but is a fractured analogue when it comes to dates. 𝕀s have to take this weirdness in their stride.

The heart of an 𝕀 is a sign, year, month and day counts. That's fairly simple, but there's a key extra element of precision. For example +1y 1m 1d appears to be accurate to a day, but what about +1y 1m 0d? Is it just that we don't happen to have any days, or is it that the calculations we performend to generate the 𝕀 were only accurate to a month? Without a precision we're reduced to making assumptions that may not be true. How old is your cat is unlikely to be +7y 0m 0d accurate to a day or even a month. In the case of my cat, seven years is a guess.

We're also going to have to record in our 𝕀 oddity situations where we couldn't compute an interval. Think of the semantic difference between How long have you had your leg in plaster? and How long was your leg in plaster? DAY can answer both those questions but then we need to stick the answer carefully into an 𝕀 ... store... retrieve... and process sensibly. (Until the plaster comes off we don't have closure. Before that the answer is six days and counting)

We need to distinguish between an 𝕀 that has actual errors, typically as a result of illegal operations or malformed data, and one that is peculiar. Then we're hoing to have to feed some of that back into 𝔻 methods. All-in-all, hours of fun for a wet afternoon!

Is an Interval a Day?

Sticky question! Originally as the DAY specification was concieved, DAYs could represent intervals. This was cute for data storage and of course there are very close interactions, but as of now we'll treat the two things as separate classes. However we can use a single 32-bit representation for both.

Numeric limitations

We're in a bit of a minefield as (a) we can't reliably convert between days and months, and (b) we may need to store large numbers in 32 bits. Precision confuses things more as we may need to change the precision we're working with to match other data in our application.

Do I need to use intervals to get full functionality from normal DAYs?

The power of intervals is important when you're doing fancy things such as 'adding' or 'subtracting' dates. Unfortunatly hacks and oddities go with the territory, so having Intervals to hide all that gruseome fudging is great. In the first instance when you want to do this sort of manipulation with dates, firstly check there is no suitable DAYo method that will do the job. If not then just use intervals as if they were ordinary scalar values and see what happens. Now test for some of the more unusual values. Try to avoid anything that requires a conversion between days and months or years if you can, and watch out for shifting precision.

Interval constructor

This takes various types of initialiser as shown in the table below.
InitialiserNotes
stringThis is a reliable way to specify intervals and have the precision automatically set as well. See .FromString()
number <4096Small magnitude numbers are assumed to be days.
number >4095Assumed to be an unsigned 32-bit integer.1 See .From32Bits()
arraySee .FromArray()
DAYiMake a copy
DAYoExtract from a 𝔻.2 See .ReadDAYo()
undefinedAllowed but not very useful for the general applications programmer.
1Javascript doesn't have strict numeric types. However the binary construction of '32-bits numbers' is such that they can be represented as unsigned integers.
2Originally DAYos were designed to accomodate intervals as well as all the other types of date paraphenalia. However in the main it's simpler to keep them separate. This may change with time.

Convenience constructors

DAYu.MakeINT
𝕀
(Years,Months,Days)
DAYu.SumIntervals
𝕀
(array of 𝕀s)

Interval properties

In general it's a bad idea to access these properties directly. Using methods is safer, more reliable and will deal with tricky details.
.signature integer DAYu.INT or DAYu.NVI
  • Use the method 𝕀.IsValid() to test.
.positive boolean
  • Use the method 𝕀.IsPositive() to test.
  • Use the methods 𝕀.MakePositive() and 𝕀.MakeNegative() to change.
.days integer
  • Always a positive (or zero) integer. While used in live calculations there is no reasonable limit but conversion to DAYo or 32-bits you shouldn't have more than 4095 days.
  • Normally you'd use .SetYMD() to change as that will deal with all the precision issues.
.months integer
  • Always a positive (or zero) integer. While used in live calculations there is no reasonable limit but large numbers of months can be converted accurately into years, and will be if there is any need to convert storage formats.
  • Normally you'd use .SetYMD() to change as that will deal with all the precision issues.
.years integer
  • Always a positive (or zero) integer. While used in live calculations there is no reasonable limit but conversion to DAYo or 32-bits you shouldn't have more than 4095 days.
  • Normally you'd use .SetYMD() to change as that will deal with all the precision issues.
.errorCode integer 0..7 When something goes wrong (in the sense of strange data or combinations of inputs) you can look at this to get a rough idea why.
  • The meanings of the error codes are sepecified in DAYu as INTERR_... constants
  • The .error property should not be relied on unless the .signature is INV
  • See PrecisionOrError()
.precision integer 0..3 Is the precision years, months or days.
  • The meanings of the precision vaklues are sepecified in DAYu as INTPRES_... constants.
  • The .precision property should not be relied on unless the .signature is INT.
  • There may be cases where a valid INT has unknown precision (INTPRES_X). Normally this is where a there's not enough data.
  • See PrecisionOrError()

Interval methods

𝕀.Add
𝕀
(one 𝕀 or an array of 𝕀s , boolean UseDurations)
What could be simpler than adding two intervals? The intention is simple but the implementation is subject to all sorts of difficulties. For example add +1y to +1d. Is that the same as adding +1y 0m 0d to +1d. Nope! This is why you have to understand precision and relate that to your purpose.
Difficulty explained. Adding to +1d
Argument+1y+1y 0m 0dSame thing right?...
PrecisionYearsDays...NO!
ArithmeticAdd 1/365th of a year to a yearAdd 365.25 days to 1 dayThat looks different
Sum1.0027 years366.25 daysSame thing -ish?
Result+1y+366dClose, but see below.
Result precisionYearsDaysWe don't suddenly get greater precision because the precision of a result depends on the least precise input.
Difficulty explained (2) Adding to -200d
Argument+1y+1y 0m 0d
PrecisionYearsDays
ArithmeticSubtract 200/365th of a year from 1 yearSubtract 200 days from 365.25
Sum0.452 years165.25 daysSame thing -ish?
Result+0y+165dOver 5 months difference!
Result precisionYearsDaysSo be careful!
The moral is try to stick to one precision and you'll be fine, but as soon as you get weaker precision then all the subsequent results will adopt that weaker precision.

If the optional boolean argument UseDurations is true then the addition algorithm is different. In Interval-world a duration is a number of days. This is fantastic except for the difficuly converting between days to months and years. Often this will be just the ticket.

  • If this or any of the operands is NVI then the result will be NVI with an error code of INTERR_USED_NV.
  • Warning. Add 100 days 5 times to +1y and it will never get to +2y.
  • It's perfectly normal to 'add' intervals with different signs but be careful if you are going to end up with mixed signs for the elements then it's possible that your result will be converted to days duration... except they might get lost in the battle of least precisions. Here's an example. We add an array of +1y 2m and -3m to +0y 0m. The 'result' will be +1y -1m which is illegal as it stands. So, because the precisions are all monthly we can reduce this to a legal version of -11m.
  • DAYu.SumIntervals is a constructor/wrapper for this method. That takes an array of 𝕀s and returns a new 𝕀
𝕀.@
@
()
𝕀.@
@
()
𝕀.@
@
()
𝕀.@
@
()
𝕀.@
@
()
𝕀.@
@
()
𝕀
DAYu.
array
(number dayCount) DayCount is a number of days (Not necessarily an integer) Return an array of [Y,M,D] after applying the necessary conversions
  • Uses the fractional conversion factor DAYu.DAYSINMONTH