= Handling dates = There are different ways to represent dates. No one way is satisfactory for all purposes. `DateTime` objects work well for most, if you do not have too many of them. When you do, you will want other representations, according to what you need to get done. == DateTime objects == A .Net `DateTime` object displays in readable form, and supports the comparison functions and also addition and subtraction. You can include them in arrays with other kinds of data. They have to be serialised to be stored, but that is little trouble when they can represent themselves as readable strings and be as easily reinstantiated from them. Here are some examples of handling dates as `DateTime` objects. {{{ ⎕USING←'System' +dt←DateTime.Now ⍝ displays readably 07/01/2008 16:56:50 ⍴⍴dt ⍝ but is scalar 0 ⍴dt.⎕NL-2 3 ⍝ has many properties and methods 59 ⍴⎕←dt.ToShortDateString ⍝ represent as character vector 07/01/2008 10 }}} The class offers many useful methods. {{{ dt.IsDaylightSavingTime 0 DateTime.Parse∘⊂¨'15/9/52' '15SEP52' '15 September 1952' 15/09/1952 00:00:00 15/09/1952 00:00:00 15/09/1952 00:00:00 DateTime.IsLeapYear 2007 0 DateTime.DaysInMonth¨(2007 2)(2008 2) 28 29 }}} Comparison functions extend to the objects. {{{ +bd←DateTime.New 1952 9 15 ⍝ SJT’s birthday 15/09/1952 00:00:00 ⍝ and now his whole family ∆←('mum' 1930 5 24)('dad' 1926 9 3) ∆,←('caro' 1960 11 16)('jo' 1954 7 5)('me' 1952 9 15) +fmly←↑{⍵[1],DateTime.New 1↓⍵}¨∆ mum 24/05/1930 00:00:00 dad 03/09/1926 00:00:00 caro 16/11/1960 00:00:00 jo 05/07/1954 00:00:00 me 15/09/1952 00:00:00 ⊃∘⊃∘⎕CLASS¨fmly[;2] ⍝ 2nd column contains DateTime objects System.DateTime System.DateTime System.DateTime System.DateTime … {⍎⍺,'←⍵'}/fmly ⍝ associate names and dates mum=dad ⍝ equivalence 0 fmly[;2]⍳jo caro ⍝ lookup 4 3 mum⌊dad ⍝ comparison 03/09/1926 00:00:00 fmly[⍋fmly[;2];1] ⍝ names in birth order dad mum me jo caro }}} Subtraction extends to the objects, returning `TimeSpan` objects. {{{ mum-dad ⍝ days between my parents' births 1359.00:00:00 ⊃⊃⎕CLASS mum-dad System.TimeSpan (2-/caro jo me mum dad).Days ⍝ days between all our births 2326 658 8150 1359 }}} For modest amounts of data it is hard to improve on the versatility of `DateTime` objects. The obvious limit to this strategy is the number of `DateTime` objects you create and destroy. When handling large tables and vectors you might need to find lighter, faster ways to handle dates. That is what this section is about. == Other formats == * '''Character vectors''', such as `15/09/1952 06:30:00`, have the advantage of being human-readable. This is a solid merit when you are developing with an interpreter and can stop and examine things. This is a useful way to store dates in text files but, given what RDBM systems and XML parsers can do, it is too restrictive for databases or XML documents. * '''Numeric vectors''': 6- or 7-element numeric vectors that follow the format of the system clock `⎕TS`. They are readable, but not convenient for the more common calculations with dates. But where you want access to the parts (years, months and so on) of dates, they can be just what you need. While numeric timestamps might be an awkward format in other languages, Dyalog’s APL roots make them easy to handle. * '''Numeric scalars''', such as `19520915`, are readable and support comparison functions such as `= ≠ > < ≤ ≥ ⍒ ⍋ ⌈ ⌊`. By using floating-point numbers you can include time of day as well, though you sacrifice some readability. For example, 6.30am on that date would be `19520915.063000`. However, you cannot use the important subtract function on dates, though you often want it. * '''OA dates and IDNs''' number the days since an chosen start date. IDNs start from 1 Jan 1900; OA dates (a Microsoft standard used in Office applications) from 31 Dec 1899. These open your dates to subtraction, but at the cost of readability. As with simple numbers, above, times can be included by using real numbers, and as easily razored off (mnemonic) with the ''modulus'' function `|`. * '''Day numbers''' number the days of the week: 0 through 7. With IDNs, 0s are Sundays; with OA dates, Saturdays. == Switching formats == If you need a lighter representation than `DateTime` items, you will need some ways of converting between them. We will look first at converting between `DateTime` objects and other forms, then at converting between the other forms. Finally, we shall look at algorithms that take advantage of particular representations. === DateTime and character === `DateTime` objects, being objects, are scalars. They display readably in the session. Applying the format function `⍕` is enough to secure the default display as a character vector. {{{ +now←DateTime.Now 10/01/2008 18:30:40 ⍴⍴now 0 ⍴⎕←⍕now 10/01/2008 18:30:40 19 ⍴⎕←fmly mum 24/05/1930 00:00:00 dad 03/09/1926 00:00:00 caro 16/11/1960 00:00:00 jo 05/07/1954 00:00:00 me 15/09/1952 00:00:00 5 2 ⍴⎕←⍕fmly mum 24/05/1930 00:00:00 dad 03/09/1926 00:00:00 caro 16/11/1960 00:00:00 jo 05/07/1954 00:00:00 me 15/09/1952 00:00:00 5 27 ⍴⎕←⍕¨fmly mum 24/05/1930 00:00:00 dad 03/09/1926 00:00:00 caro 16/11/1960 00:00:00 jo 05/07/1954 00:00:00 me 15/09/1952 00:00:00 5 2 }}} Doesn’t get much easier than that. `DateTime` objects also offer methods for specific date formats, such as: {{{ ⍴¨⎕←(me jo).ToShortDateString 15/09/1952 05/07/1954 10 10 }}} These have the usual .Net advantage of honouring the internationalisation settings on the target machine. In similar manner, `DateTime` can construct instances from character vectors containing a wide range of date formats. {{{ DateTime.Parse∘⊂¨'15SEP52' '1952-09-15' 15/09/1952 00:00:00 15/09/1952 00:00:00 }}} === Numeric vectors === This could hardly be easier. {{{ ⍴⎕←me.(Year Month Day) 1952 9 15 3 ⍴⎕←me.(Year Month Day Hour Minute Second) 1952 9 15 0 0 0 6 DateTime.New 1952 9 15 15/09/1952 00:00:00 DateTime.New 1952 9 15 6 30 0 15/09/1952 06:30:00 me=DateTime.New 1952 9 15 1 fmly[;,1],(fmly[;2]).(Year Month Day) mum 1930 5 24 dad 1926 9 3 caro 1960 11 16 jo 1954 7 5 me 1952 9 15 }}} === Numeric scalars === `DateTime` objects don’t offer this representation so we convert to and from numeric vectors. {{{ 100⊥1952 9 15 19520915 100⊥1952 9 15 6 30 0 1.952091506E13 0 100 100⊤19520915 1952 9 15 0 100 100 100 100 100⊤19520915063000 1952 9 15 6 30 0 }}} Note that while we need only a scalar left argument to the encode ⊥ function, we need a vector for its inverse, the decode `⊤` function. That left argument might become a little unwieldy. It could be more convenient to define `ed` to encode dates and `dd` as its inverse. {{{ ed←0 100 100∘⊥ dd←ed⍣¯1 ed 1952 9 15 6 30 0 19520915 dd 19520915063000 1952 9 15 fmly[;,1],ed¨(fmly[;2]).(Year Month Day) mum 19300524 dad 19260903 caro 19601116 jo 19540705 me 19520915 }}} Similarly `et` and `dt` for timestamps. {{{ et←÷∘1e6∘(0 100 100 100 100 100∘⊥) dt←et⍣¯1 et 1952 9 15 6 30 0 19520915.06 6⍕et 1952 9 15 6 30 0 19520915.063000 }}} === OA Dates and IDNs === `DateTime` objects provide methods to convert to and from OA dates. {{{ me.ToOADate 19252 DateTime.FromOADate 19252 15/09/1952 00:00:00 fmly[;,1],(fmly[;2]).ToOADate mum 11102 dad 9743 caro 22236 jo 19910 me 19252 }}} '''Watch out:''' these look like but differ from IDNs. Microsoft’s day count starts one day earlier than the international standard. {{{ (DateTime.New 1952 9 15).ToOADate 19252 #.DateToIDN 1952 9 15 19251 DateTime.FromOADate 19252.5 15/09/1952 12:00:00 #.IDNToDate 19251.5 1952 9 15 0 }}} OA Dates are exactly what you want for Microsoft applications such as Excel. They even handle fractional dates correctly. Otherwise you can use the light and fast Dyalog root methods `#.DateToIDN` and `#.IDNToDate`. {{{ fmly[;,1],#.DateToIDN¨(fmly[;2]).(Year Month Day) mum 11101 dad 9742 caro 22235 jo 19909 me 19251 }}} == Converting between other formats == Where speed matters you will avoid creating large arrays of `DateTime` objects. But different representations suit different purposes. {{attachment:dateformats.gif}} To summarise: * If you can use .Net and do not to have to create prohibitively long arrays of them, will you find `DateTime` objects most convenient. * If you have to parse or produce varied character date formats, especially in internationalised forms, you will find the `DateTime` class convenient, even if you restrict yourself to its static methods and do not create arrays of `DateTime` objects. * If you are interfacing to Microsoft applications such as Excel you will find OA dates convenient. You can create these without invoking the `DateTime` class at all, by subtracting 1 from IDNs. * If you need speed, or independence from .Net, stay on the right-hand side of the figure. Here is an example of switching between representations to simplify calculation. Calculating the next anniversary of a date is easily done in vector format. {{{ ⍝ next anniv of ⍵[2] after ⍵[1] (y m d) nxtann←{(3↑>/10000⊤100⊥⍉⍵)+⊃↓0 1 1⊖⍵}∘{↑¯3↑¨⍵} nxtann (2008 12 23)(12 25) 2008 12 25 nxtann (2008 12 27)(12 25) 2009 12 25 }}} Differences are best calculated between IDNs. {{{ onIDNs←{⊃⍺⍺/#.DateToIDN¨⍺ ⍵} ⍝ ⍺⍺ on args as IDNs 2008 12 25 -onIDNS 2008 1 11 ⍝ days to Xmas 349 }}} For shopping days, we count the days that are not Sundays. {{{ wnb←{⍺+⍳⍵-⍺+1} ⍝ whole numbers between 3 wnb 9 4 5 6 7 8 sd←{×7|⍵} ⍝ shopping days (IDNs) sd2x←{+/sd ⍺ wnb onIDNs nxtann ⍺ ⍵}∘12 25 sd2x 2008 1 11 298 }}} ---- == Case study: shopping for Jesus == {{{ 100⊥1954 7 5 19540705 0 100 100⊤19540705 1954 7 5 (100⊥⍣¯1)19540705 1954 7 5 dual←{⍺(⍺⍺⍣¯1)⍵⍵ ⍺ ⍺⍺ ⍵} 0 100 100⊤dual {⎕←⍵} 19540705 ⍝ display before recoding 1954 7 5 19540705 0 100 100⊤dual {⎕←⍵} 19540705 19520915 ⍝ my and my sister's birthdates 1954 1952 7 9 5 15 19540705 19520915 0 100 100⊤dual {⎕←⍵} 20070714 19520915 ⍝ today and my birthdate 2007 1952 7 9 14 15 20070714 19520915 0 10000⊤dual {⎕←⍵} 20070714 19520915 ⍝ split ccyy mmdd instead of ccyy mm dd 2007 1952 714 915 20070714 19520915 0 10000⊤dual {1 1⍉⎕←⍵} 20070714 19520915 ⍝ my birthday this year 2007 1952 714 915 20070915 0 10000⊤dual {1 1⍉⎕←⍵} 20071014 19520915 ⍝ but perhaps already passed 2007 1952 1014 915 20070915 0 10000⊤dual {(2↑2⊃>/⍵)+1 1⍉⎕←⍵} 20071014 19520915 ⍝ my next birthday 2007 1952 1014 915 20080915 nextbirthday←{0 10000⊤dual {(2↑2⊃>/⍵)+1 1⍉⍵} ⍺ ⍵} ⍝ as a dyadic fn 20070714 nextbirthday¨19520915 19540705 ⍝ my and my sister's next birthdays 20070915 20080705 Jo SJT KEI Jesus←0705 0915 1217 1225 ⍝ birth years optional 20070714 nextbirthday¨Jo SJT KEI Jesus 20080705 20070915 20071217 20071225 YMDToIDN←{DateToIDN 0 100 100∘⊤⍵} ⍝ convert ccyymmdd to IDN asIDN←{⊃⍺⍺/YMDToIDN¨⍺ ⍵} ⍝ perform ⍺⍺ on args as IDNs 20070915 -asIDN 20070714 ⍝ days to my next birthday 63 20070714{⍺ -⍨asIDN ⍺ nextbirthday ⍵}1225 ⍝ days to Christmas 164 nb←{⍺+⍳⍵-⍺+1} ⍝ whole numbers between args wkday←{(7|⍵)∊⍳5} ⍝ is arg a weekday? 20070714{+/wkday ⍺ nb asIDN ⍺ nextbirthday ⍵}Jesus ⍝ weekdays to Christmas 116 }}} StephenTaylor xxv.ii.MMVII ---- CategoryDyalogDotNet