Day data representation
Low-level storage formats for inter-operability
32-bits and inter-operability
Days are easily stored as 32-bit integers but there may be situations where it is convenient to use
other data representations. Typically a database may be configured with date-time column types so
you would convert a Day into a SQL compatible string to store it, and of course the reverse. One
important aspect of this is that as a Day contains more information than day, month, year we need to
hi-jack the hours and minutes of such a storage method. This hack has been designed so that existing
dates in the storage system can be read as well as the enhanced versions.
Days can inter-operate (obviously limited by what's possible by the storage medium) with Javascript Date, and Unix timestamps.
Therefore there are two main sections to this specification:
- How bits are used in the native 32-bit representation
- How Days can inter-operate either as fully qualified calendar days (D,M,Y all specified) and also
be shoe-horned with into existing date-time storage fields
Introduction
Efficient storage of Days is important as is the ability to sort sensibly out-of-the-box.
The native 32-bit encoding supports dates and intervals and is the preferred method of data
storage and transmission.
Existing systems may require interfacing with Day and this can cause issues as they have no
special values and don't support partial dates such as say 'May 2013'. For these we create
or consume hacked versions which use the hours, minutes and seconds elements to give a
reasonable coverage of important Day values. Sometimes we want to store and read these bits
and in others ignore them. For example to put a 'End-of-time' value into a legacy database
'date-time' field for later retrieval we will need the bits, while with a 'date of last login'
field they would be ignored.
In general Days should be inter-operable with existing date-time systems with minimal code changes.
All encodings should be lossless in the Day -> Encoding -> Day sequence.
For all new data storage use the 32 bit version
myUInt32var = myDay.To32Bits();
myDay = new DAYo(myUInt32var);
Native 32-bit integer
Bits | Size | Usage | Notes |
31-29 | 3 | Signature | See signature values below |
28 | 1 | Sign | 0=BC/Negative. 1=AD/+ve |
27-16 | 12 | Year | 0...4095 |
15-12 | 4 | Month | 1=Jan,12=Dec 0 is legal |
11-7 | 5 | Day of month | 1...31 0 is legal |
6-4 | 3 | Not valid reason | See error codes below |
3 | 1 | | Not used |
2 | 1 | Monthly precision | Date : 1=month is valid Interval : 1=to month precision |
1 | 1 | Daily precision | Date : 1=fully specified Interval : 1=to day precision |
0 | 1 | Interval | 1=Interval 0=Date |
Note that from an encoding point of view it is possible to have a signature of
NV followed by Y,M,D values and a not-valid reason. There is a possible use for
this where dates are being supplied in bulk by a process that can't reject the
data outright.
Intervals use these bits differently.
Bits | Size | Usage | Notes |
28 | 1 | Positive | 0=true 1=false |
27-13 | 15 | Years and days combined | Packed to maximize useful range combinations |
12-11 | 2 | Split of years and days. | 4 possibilities |
10-7 | 4 | Months | 0 to 11 |
6-4 | 3 | Precision or error | Error 0..7 if NVIor precision 0..3 |
Signatures |
Error codes@@@ check width DAYu DFS_ constants |
Decimal | Mnemonic | Description |
0 | NVI | Not valid interval |
1 | INT | Interval |
2 | NV | Not valid (date) |
3 | FLO | Floating date |
4 | NK | Not known |
5 | BOT | Beginning of time |
6 | CAL | Calendar date |
7 | EOT | End of time |
|
Decimal | Date | Interval |
0 | Unknown | Unknown |
1 | An unsuitable string or date was provided | Input too short or long |
2 | | Too few or too many tokens |
3 | Unable to interpret a non-numeric element | Initial sign missing |
4 | Year element is missing, illegal or not 2 or 4 characters. Or month must be followed by year or day | Unknown unit |
5 | Unable to parse (eg dmmyy needs an 0 at the front.) or can't tell day-month order | Sign misplaced |
6 | Unsuitable number element | |
7 | Function failure | |
|
Note that if these (unsigned) integers are sorted the most significant bits are the signatures followed by the year then month then day. This has the pleasant result that BoT comes before sorted dates which are followed by EoT. Furthermore 1982 comes before January 1982 which comes before 1st January 1982.
Strings
See below for inter-operation with existing databases etc.
We define a simple string format that can be used for JSON say, and is easy for other applications to read and write. Use this when the full functionality of dates is required. (If you just want fullly specified CALs then you could use YYYY-MM-DD.) This is a string with the elements separated by the tilde (~) character.
signature~sign~year~month~day~error where (case insensitive)
signature | is one of | NVI,INT,NV,FLO,NK,BoT,Cal,EoT | defined in | DAY.SIGNATURE_STRINGS |
sign | is one of | -ve,+ve,-,+ | defined in | DAY.SIGN_STRINGS |
- Obviously any interacting application will need to 'understand' the way Day works.
- This is ideal for storage where a string is preferred to an integer.
- A variant is allowed which preserves the sorting ability. The signature uses the numeric signature 0..7, the sign is 0(-ve) or 1(+ve) and numbers are padded with zeroes.
Intervals
An interval always starts with a sign then one to three numbers with y, m or d suffix. The order doesn't matter. For example +1y 2m 3d is the same as +2m 3d 1y
- integer values for the y, m and d parameters are not limited to 4095, 12 and 31. So for example +0y 45m 234d is valid. It's perfectly possible to deal in days alone with say -1234d
- The leading sign applies to all elements and an intermediate sign is an exception
- Missing least significant elements are important. These indicate the precision (or lack of it). So for example +1y +2m +0d is not the same as +1y +2m as the second is precise to months only.
Converting to/from other date formats
Common method
Warning: These functions are hacks where you're stuck with an existing data format.
Suppose you have to interface with an existing SQL database or one where other, less Day-savvy, applications will be dipping-in too. Clearly the bottom line is that you can only use fully specified dates. This clobbers the raison-d'etre of Day. But here is a ray of hope that could be used to spread a little happiness. The spare H:M:S parts of the timestamp are hi-jacked to provide the extra information a Day object can use.
Day signature |
D M Y |
H M S |
Comment |
NVI |
30 Dec 4096 BC |
10 reason 0 |
|
INT |
30 Dec 4096 BC |
1 0 0 |
Not supported |
NV |
30 Dec 4096 BC |
2 reason 0 |
|
FLO |
d m 0 |
3 mask 0 |
JS allows year zero! |
NK |
31 Dec 4096 BC |
4 0 0 |
|
BOT |
31 Dec 4096 BC |
5 0 0 |
|
Earliest CAL |
1 Jan 4095 BC |
0 0 0 |
Fully specified CALs will
always have 00:00:00 time |
CAL |
d m y |
0 mask 0 |
Latest CAL |
31 Dec 4095 |
0 0 0 |
EOT |
1 Jan 4096 |
7 0 0 |
|
A nice feature is that, provided you make sure no spurious hours creep in, other applications can do a simple switch on the hours for NV,NK,BoT and EoT which are great low-hanging fruit.
GOTCHA! You'll need to know what interfaces with this data and make sure it's documented, otherwise you could end up with spurious values propagating in all directions.
Conversion functions
For all new data storage use the 32 bit version
| myInt32var = myDay.To32Bits();
myDay = new DAYo(myInt32var);
|
Javascript dates use
| myJsDate = myDay.ToDate(); // if h,m,s in myJsDate are 0 then y,m,d are honest
myDay = new DAYo(myJsDate);
|
Database strings will probably use
| myDateTimeStr = myDay.ToYmdhms('YMDHMS');
myDay = new DAYo(myDateTimeStr, @@@DAYu.strTypeExtendedUnix);
|