# Finding Gaps in Date Ranges

Given a list of date ranges r as input, output or return any ranges not found in r.

For the sake of this example, input will be in YYYY-MM-DD format.

Let's say you have three date ranges:

[2019-01-01, 2019-02-01]
[2019-02-02, 2019-04-05]
[2019-06-01, 2019-07-01]


You can see that there is a gap in between 2019-04-05 and 2019-06-01.

The output will be that gap: [2019-04-06, 2019-05-31]

## Rules

• Input and output can be in any reasonable date or collection format, as long as it is consistent.
• Assume the input is not ordered.
• Your date range does not have to be [latest, earliest], but it does have to follow rule 2.
• Assume there are no overlapping dates in the input

## Test Cases:

Input: [[2019-01-01, 2019-02-01],[2019-02-02, 2019-04-05],[2019-06-01, 2019-07-01]]

Output: [[2019-04-06, 2019-05-31]]

Input: [[2019-01-01, 2019-02-01],[2018-02-02, 2018-04-05],[2019-06-01, 2019-07-01]]

Output: [[2018-04-06, 2018-12-31], [2019-02-02, 2019-05-31]]

Input: [[2019-01-01, 2019-02-01],[2019-02-02, 2019-03-02],[2019-03-03, 2019-07-01]]

Output: []

Input: [[2019-01-01, 2019-02-01], [2019-11-02, 2019-11-20]]

Output: [[2019-02-02, 2019-11-01]]

Input: [[2019-01-01, 2019-02-01],[2019-02-03, 2019-04-05]]

Output: [[2019-02-02, 2019-02-02]] or [[2019-02-02]]

• I suggest reworking all the example dates to ISO format, YYYY-MM-DD as the current format is both foreign to many people, and made even harder to parse due to using small days-of-month≤12. – Adám Feb 18 '19 at 15:57
• @Adám Good idea, updated. – Oliver Feb 18 '19 at 16:08
• May we take input as .NET OLE Automation Dates? – Adám Feb 18 '19 at 16:22
• @Adám Yes. Any reasonable date format is acceptable. – Oliver Feb 18 '19 at 16:23
• Will the dates be ordered? Also, within a pair of dates, will the later one be last? – Gymhgy Feb 18 '19 at 17:45

# APL (Dyalog Extended), 2825 24 bytes

Anonymous tacit prefix function. Argument and result are 2-column matrices of day numbers since an epoch, each row representing a range.

1 ¯1+⍤1∘{⍵⌿⍨1<-⍨/⍵}1⌽⍢,∧


Try it online! The In pre-processor function converts from a list of pairs of 3-element lists (dates in ISO order) to a 2-column matrix of IDNs, International Day Numbers (days since 1899-12-31). The Out post-processor function converts from a matrix of IDNs to a matrix of 3-element lists.

∧ sort rows ascending

1⌽ cyclically rotate the dates one step left
⍢, while ravelled (flattened) — afterwards, reshape back to original shape

1 ¯1+ add one and negative one
⍤1 using that list for each row
∘ of the result of
{} the following lambda:
⍵ the argument
-⍨/ subtract left-hand date from right-hand date, row-wise
1< mask where differences exceed one (i.e. where ranges are not adjacent)
⍵⌿⍨ filter the rows by that mask

# C# (Visual C# Interactive Compiler), 108 bytes

n=>{n.Sort();for(int i=0;;)Write(n[i].b.AddDays(1)==n[++i].a?"":n[i-1].b.AddDays(1)+""+n[i].a.AddDays(-1));}


Outputs by printing in the format DD/MM/YYYY 12:00:00 AMDD/MM/YYYY 12:00:00 AM. Will cause an IndexOutOfRange exception, which is fine per meta consensus.

Try it online!

If we take input in the form of days since the unix epoch, we can get this down to...

# 83 bytes

n=>{n.Sort();for(int i=0;;)Print(n[i].b+1==n[++i].a?"":n[i-1].b+1+" "+(n[i].a-1));}


Try it online!

We can golf this down even further with the /u:System.Array flag, for...

# 78 bytes

n=>{Sort(n);for(int i=0;;)Print(++n[i].b==n[++i].a--?"":n[i-1].b+" "+n[i].a);}


Try it online!

# Perl 5, 130 bytes

/-(\d+)-/,$_=strftime"%Y-%m-%d",0,0,0,$'+($|--||-1),$1-1,$-1900 for@F=sort@F;$,lt$;&&say"$, $;"while($,,$;)=@F[++$i,$i+1],++$i<@F


TIO

# Bash, 125 bytes

set sort<<<$1;shift;for a;{ s=$[x++%2?-1:1]day;c=date -d$a\$s +%Y-%m-%d;[ $p ]&&{ [[$p < $c ]]&&echo$p $c;p=;}||p=$c;}


TIO

# Perl 6, 46 bytes

{@_.sort[1..*-2].map:{$^a+1,$^b-1 if $b>$a+1}}


Try it online!

Takes a list of Date pairs.

# PHP, 208 197 190 177 bytes

hunky chunky sat on a wall ... though the new approach had quite some golfing potential.

function($a){sort($a);for($m=$x=$a[0][0];$f=$m<=$x;$f^$g&&print($g=$f)?"$m/":"$n
",$m=date("Y-m-d",strtotime($n=$m)+9e4))foreach($a as$d)$x=max($x,$d[1|$f&=$m<$d[0]|$m>$d[1]]);}  function takes array of ranges [start,end] in ISO format, prints gap intervals. Try it online. ## breakdown function($a){
sort($a); # sort ranges (for easy access to min date) for($m=$x=$a[0][0];$f=$m<=$x; # loop from min date to max date, 1. set flag$f^$g&&print($g=$f)?"$m/":"$n\n", # 4. flag changed: backup flag, print date$m=date("Y-m-d",strtotime($n=$m)+9e4)   # 5. backup and increment date
)foreach($a as$d)
$x=max($x,$d[1 # 2. find max date |$f&=$m<$d[0]|$m>$d[1]              # 3. date found in ranges: clear flag
]);
}


# Jelly, 13 bytes

FṢṖḊs2+Ø+>/Ðḟ


Jelly (currently) has no built-in dates, so this uses days-since-epoch.
The input list of ranges (pairs of integers) may be in mixed order and mixed directions.
The result is a list of ascending ranges in ascending order.

Try it online! (footer formats in order to show an empty list as [])

### How?

Note: This relies on the assurance that "there are no overlapping dates in the input" as stated in the rules.

FṢṖḊs2+Ø+>/Ðḟ - Link: list of pairs of integers
F             - flatten
Ṣ            - sort
Ṗ           - pop (remove tail)
s2        - split into twos
Ø+     - literal [1,-1]
Ðḟ - filter discard those for which:
/   -   reduce by:
>    -     greater than?

• Interesting, I didn't know that Jelly didn't have date support. Is this the usual approach? Use days since epoch? – dana Feb 21 '19 at 1:23
• Days since epoch is, I believe, used by some systems (Excel maybe). Seconds since epoch is more common (e.g Unix). I just went with something which seems to cover the requirements, albeit fairly laxly. – Jonathan Allan Feb 21 '19 at 14:54
• Boo, you could calculate the dates manually. ;P Days since epoch is indeed used more often for languages that doesn't support dates. I do feel like it makes this challenge A LOT easier, though. – Kevin Cruijssen Feb 22 '19 at 8:08
• @KevinCruijssen heh, agreed. – Jonathan Allan Feb 22 '19 at 17:01

# C# (Visual C# Interactive Compiler), 103 bytes

x=>{var(a,_)=x[0];foreach(var(b,c)in x.OrderBy(y=>y)){if(a<b)Print((a,b.AddDays(-1)));a=c.AddDays(1);}}


Try it online!

Input is a list of start/end date tuples. Outputs each missing range to STDOUT.

// x: input list of start/end date tuples
x=>{
// variable definitions...
// a: 1 day after the end date of the previous range
// b: start of the current range
// c: end of the current range

// start by deconstructing the start date of the first tuple
// into a. a will then be a DateTime and will contain a value
// at least a large as the smallest start date.
var(a,_)=x[0];
// iterate over sorted ranges
foreach(var(b,c)in x.OrderBy(y=>y)){
// if the day after the end of the previous range is less
// than the start of the current range, then print the
// missing days.
if(a<b)
// save the day after the current range to a for next iteration
}
}

• 128 – ASCII-only Feb 20 '19 at 15:42
• Hah - if you do print like Embodiment of Ignorance you can get really small - Try it online! – dana Feb 20 '19 at 16:05
• Nice. Also wtf their input method for the latter two – ASCII-only Feb 20 '19 at 16:09
• Well actually... that looks really incorrect – ASCII-only Feb 20 '19 at 16:11
• Yeah that looks fine now – ASCII-only Feb 20 '19 at 16:25

# R, 88 bytes

function(a,b=a[order(a$x),],d=c(b$x[-1]-b$y[-nrow(b)],0))data.frame(b$y+1,b\$y+d-1)[d>1,]


Try it online!

This takes a data frame of date ranges as input and outputs a data frame with the ranges that are missing. I’m fairly sure this could be golfed more, but I ran into issues with c, cbind` and others stripping the date class.