A estrutura de agregação é definitivamente a abordagem correta - qualquer coisa que exija JS no servidor é um problema de desempenho, enquanto as agregações são executadas no servidor em código nativo.
Embora seja possível transformar o aniversário em datas de aniversários futuros e, em seguida, fazer uma consulta de intervalo, prefiro fazer isso de uma maneira um pouco diferente.
O único "pré-requisito é calcular o dia de hoje do ano". Existem maneiras de fazer isso em várias linguagens, então isso pode ser feito na camada de aplicação antes de chamar a agregação, passando esse número para ela. Eu ia ligar para o meu
todayDayOfYear
mas percebi que você pode deixar a estrutura de agregação descobrir com base em hoje, então a única variável será a data de hoje. var today=new Date();
Estou assumindo um documento que inclui nome e aniversário, ajuste adequadamente para variações
var p1 = { "$project" : {
"_id" : 0,
"name" : 1,
"birthday" : 1,
"todayDayOfYear" : { "$dayOfYear" : today },
"dayOfYear" : { "$dayOfYear" : "$birthday"}
} };
Agora, projete quantos dias de hoje até o próximo aniversário:
var p2 = { "$project" : {
"name" : 1,
"birthday" : 1,
"daysTillBirthday" : { "$subtract" : [
{ "$add" : [
"$dayOfYear",
{ "$cond" : [{"$lt":["$dayOfYear","$todayDayOfYear"]},365,0 ] }
] },
"$todayDayOfYear"
] }
} };
Exclua todos, exceto aqueles dentro do intervalo desejado:
var m = { "$match" : { "daysTillBirthday" : { "$lt" : 31 } } };
Agora execute a agregação com:
db.collection.aggregate( p1, p2, m );
para obter de volta uma lista de nomes, aniversários e dias até o aniversário para todos os sortudos cujo aniversário é dentro de 30 dias.
EDITAR
@Sean999 capturou um caso interessante - as pessoas que nasceram em um ano bissexto após 28 de fevereiro terão seus cálculos errados em um. Veja a seguir uma agregação que se ajusta corretamente a isso:
var p1 = { "$project" : {
"_id" : 0,
"name" : 1,
"birthday" : 1,
"todayDayOfYear" : { "$dayOfYear" : ISODate("2014-03-09T12:30:51.515Z") },
"leap" : { "$or" : [
{ "$eq" : [ 0, { "$mod" : [ { "$year" : "$birthday" }, 400 ] } ] },
{ "$and" : [
{ "$eq" : [ 0, { "$mod" : [ { "$year" : "$birthday" }, 4 ] } ] },
{ "$ne" : [ 0, { "$mod" : [ { "$year" : "$birthday" }, 100 ] } ] } ] } ] },
"dayOfYear" : { "$dayOfYear" : "$birthday" } } };
var p1p = { "$project" : {
"name" : 1,
"birthday" : 1,
"todayDayOfYear" : 1,
"dayOfYear" : { "$subtract" : [
"$dayOfYear",
{ "$cond" : [ { "$and" : [ "$leap", { "$gt" : [ "$dayOfYear", 59 ] } ] }, 1, 0 ] } ] }
}
}
p2
e m
fique igual ao anterior. Entrada de teste:
db.birthdays.find({},{name:1,birthday:1,_id:0})
{ "name" : "Ally", "birthday" : ISODate("1975-06-12T00:00:00Z") }
{ "name" : "Ben", "birthday" : ISODate("1968-04-03T00:00:00Z") }
{ "name" : "Mark", "birthday" : ISODate("1949-12-23T00:00:00Z") }
{ "name" : "Paul", "birthday" : ISODate("2014-03-04T15:59:05.374Z") }
{ "name" : "Paul", "birthday" : ISODate("2011-02-07T00:00:00Z") }
{ "name" : "Sean", "birthday" : ISODate("2004-01-31T00:00:00Z") }
{ "name" : "Tim", "birthday" : ISODate("2008-02-28T00:00:00Z") }
{ "name" : "Sandy", "birthday" : ISODate("2005-01-31T00:00:00Z") }
{ "name" : "Toni", "birthday" : ISODate("2009-02-28T00:00:00Z") }
{ "name" : "Sam", "birthday" : ISODate("2005-03-31T00:00:00Z") }
{ "name" : "Max", "birthday" : ISODate("2004-03-31T00:00:00Z") }
{ "name" : "Jen", "birthday" : ISODate("1971-04-03T00:00:00Z") }
{ "name" : "Ellen", "birthday" : ISODate("1996-02-28T00:00:00Z") }
{ "name" : "Fanny", "birthday" : ISODate("1996-02-29T00:00:00Z") }
{ "name" : "Gene", "birthday" : ISODate("1996-03-01T00:00:00Z") }
{ "name" : "Edgar", "birthday" : ISODate("1997-02-28T00:00:00Z") }
{ "name" : "George", "birthday" : ISODate("1997-03-01T00:00:00Z") }
Saída:
db.birthdays.aggregate( p1, p1p, p2, {$sort:{daysTillBirthday:1}});
{ "name" : "Sam", "birthday" : ISODate("2005-03-31T00:00:00Z"), "daysTillBirthday" : 22 }
{ "name" : "Max", "birthday" : ISODate("2004-03-31T00:00:00Z"), "daysTillBirthday" : 22 }
{ "name" : "Ben", "birthday" : ISODate("1968-04-03T00:00:00Z"), "daysTillBirthday" : 25 }
{ "name" : "Jen", "birthday" : ISODate("1971-04-03T00:00:00Z"), "daysTillBirthday" : 25 }
{ "name" : "Ally", "birthday" : ISODate("1975-06-12T00:00:00Z"), "daysTillBirthday" : 95 }
{ "name" : "Mark", "birthday" : ISODate("1949-12-23T00:00:00Z"), "daysTillBirthday" : 289 }
{ "name" : "Sean", "birthday" : ISODate("2004-01-31T00:00:00Z"), "daysTillBirthday" : 328 }
{ "name" : "Sandy", "birthday" : ISODate("2005-01-31T00:00:00Z"), "daysTillBirthday" : 328 }
{ "name" : "Paul", "birthday" : ISODate("2011-02-07T00:00:00Z"), "daysTillBirthday" : 335 }
{ "name" : "Tim", "birthday" : ISODate("2008-02-28T00:00:00Z"), "daysTillBirthday" : 356 }
{ "name" : "Toni", "birthday" : ISODate("2009-02-28T00:00:00Z"), "daysTillBirthday" : 356 }
{ "name" : "Ellen", "birthday" : ISODate("1996-02-28T00:00:00Z"), "daysTillBirthday" : 356 }
{ "name" : "Fanny", "birthday" : ISODate("1996-02-29T00:00:00Z"), "daysTillBirthday" : 356 }
{ "name" : "Edgar", "birthday" : ISODate("1997-02-28T00:00:00Z"), "daysTillBirthday" : 356 }
{ "name" : "Gene", "birthday" : ISODate("1996-03-01T00:00:00Z"), "daysTillBirthday" : 357 }
{ "name" : "George", "birthday" : ISODate("1997-03-01T00:00:00Z"), "daysTillBirthday" : 357 }
{ "name" : "Paul", "birthday" : ISODate("2014-03-04T15:59:05.374Z"), "daysTillBirthday" : 360 }
Você pode ver que as pessoas com o mesmo aniversário agora têm o mesmo número de dias até o aniversário, quer tenham nascido em um ano bissexto ou não. A etapa de correspondência agora pode ser executada para o corte projetado.
EDITAR
A partir da versão 3.5.11, existem várias expressões de manipulação de data no pipeline de agregação que tornam isso significativamente mais simples de escrever. Em particular, a expressão $dateFromParts permite construir uma data de várias partes, permitindo esta agregação:
var today = new Date();
var a1 = {$addFields:{
today:{$dateFromParts:{year:{$year:today},month:{$month:today},day:{$dayOfMonth:today}}},
birthdayThisYear:{$dateFromParts:{year:{$year:today}, month:{$month:"$birthday"}, day:{$dayOfMonth:"$birthday"}}},
birthdayNextYear:{$dateFromParts:{year:{$add:[1,{$year:today}]}, month:{$month:"$birthday"}, day:{$dayOfMonth:"$birthday"}}}
}};
var a2 = {$addFields:{
nextBirthday:{$cond:[ {$gte:[ "$birthdayThisYear", "$today"]}, "$birthdayThisYear", "$birthdayNextYear"]}
}};
var p1 = {$project:{
name:1,
birthday:1,
daysTillNextBirthday:{$divide:[
{$subtract:["$nextBirthday", "$today"]},
24*60*60*1000 /* milliseconds in a day */
]},
_id:0
}};
var s1 = {$sort:{daysTillNextBirthday:1}};
db.birthdays.aggregate([ a1, a2, p1, s1 ]);
Você pode definir "hoje" para qualquer data (ano bissexto ou não) e ver que o cálculo agora está sempre correto e muito mais simples.