¿Cómo obtener la diferencia entre dos fechas en Año / Mes / Semana / Día?

¿Cómo obtener la diferencia entre dos fechas en Año / Mes / Semana / Día de una manera eficiente?

p.ej. la diferencia entre dos fechas es 1 año, 2 meses, 3 semanas, 4 días.

La diferencia representa el recuento de año (s), mes (es), semana (s) y día (s) entre dos fechas.

Esto es realmente bastante complicado. Un número total de días diferente puede dar como resultado el mismo resultado. Por ejemplo:

  • 19 de junio de 2008 al 19 de junio de 2010 = 2 años, pero también 365 * 2 días

  • Del 19 de junio de 2006 al 19 de junio de 2008 = 2 años, pero también 365 + 366 días debido a los años bisiestos

Es posible que desee restar años hasta llegar al punto en el que tiene dos fechas con menos de un año de diferencia. Luego, resta meses hasta que llegues al punto en el que tienes dos fechas con menos de un mes de diferencia.

Confusión adicional: restar (o agregar) meses es difícil cuando puede comenzar con una fecha del “30 de marzo”, ¿qué hay un mes antes que eso?

Incluso confusión adicional ( puede no ser relevante): incluso un día no siempre es 24 horas. La luz del día a alguien?

Incluso confusión adicional (casi con seguridad no relevante): incluso un minuto no siempre es 60 segundos. Los segundos intercalares son altamente confusos …

No tengo el tiempo para encontrar la forma correcta de hacer esto en este momento; esta respuesta es principalmente para plantear el hecho de que no es tan simple como podría parecer.

EDITAR: Desafortunadamente no voy a tener tiempo suficiente para responder esto completamente. Sugeriría que comiences definiendo una estructura que represente un Period :

 public struct Period { private readonly int days; public int Days { get { return days; } } private readonly int months; public int Months { get { return months; } } private readonly int years; public int Years { get { return years; } } public Period(int years, int months, int days) { this.years = years; this.months = months; this.days = days; } public Period WithDays(int newDays) { return new Period(years, months, newDays); } public Period WithMonths(int newMonths) { return new Period(years, newMonths, days); } public Period WithYears(int newYears) { return new Period(newYears, months, days); } public static DateTime operator +(DateTime date, Period period) { // TODO: Implement this! } public static Period Difference(DateTime first, DateTime second) { // TODO: Implement this! } } 

Le sugiero que implemente primero el operador +, que debe informar el método Difference – debe asegurarse de que first + (Period.Difference(first, second)) == second para todos los valores de first / second .

Comience con la escritura de una gran cantidad de pruebas unitarias, inicialmente casos “fáciles”, luego continúe con los complicados que involucran años bisiestos. Sé que el enfoque normal es escribir una prueba a la vez, pero personalmente creo una lluvia de ideas antes de comenzar cualquier trabajo de implementación.

Permítete un día para implementar esto correctamente. Es algo complicado.

Tenga en cuenta que he omitido semanas aquí; ese valor al menos es fácil, porque siempre es de 7 días. Entonces, dado un período (positivo), tendrías:

 int years = period.Years; int months = period.Months; int weeks = period.Days / 7; int daysWithinWeek = period.Days % 7; 

(Sugiero que evites incluso pensar en períodos negativos, asegúrate de que todo sea positivo, todo el tiempo).

En parte como preparación para tratar de responder correctamente a esta pregunta (y tal vez incluso definitivamente …), en parte para examinar cuánto se puede confiar en el código pegado en SO, y en parte como ejercicio para encontrar errores, creé un grupo de pruebas unitarias para esta pregunta, y las aplicó a muchas soluciones propuestas de esta página y un par de duplicados.

Los resultados son concluyentes: ni una sola de las contribuciones del código responde con precisión a la pregunta. Actualización: ahora tengo cuatro soluciones correctas para esta pregunta, incluida la mía, consulte las actualizaciones a continuación.

Código probado

A partir de esta pregunta, probé el código de los siguientes usuarios: Mohammed Ijas Nasirudeen, Ruffin, Malu MN, Dave, pk., Jani, lc.

Estas fueron todas las respuestas que proporcionaron los tres años, meses y días en su código. Tenga en cuenta que dos de ellos, Dave y Jani, dieron la cantidad total de días y meses, en lugar de la cantidad total de meses que quedan después de contar los años y el número total de días que quedan después de contar los meses. Creo que las respuestas son incorrectas en términos de lo que el OP parecía querer, pero las pruebas unitarias obviamente no te dicen mucho en estos casos. (Tenga en cuenta que en el caso de Jani, este fue mi error y su código era en realidad correcto; consulte la Actualización 4 a continuación)

Las respuestas de Jon Skeet, Aghasoleimani, Mukesh Kumar, Richard, Colin, sheir, solo vi, Chalkey y Andy, estaban incompletas. Esto no significa que las respuestas no fueron buenas, de hecho, varias de ellas son contribuciones útiles para una solución. Simplemente significa que no había código que tomara dos DateTime y devolviera 3 int s que pudiera probar correctamente. Sin embargo, cuatro de ellos hablan sobre el uso de TimeSpan . Como mucha gente ha mencionado, TimeSpan no devuelve recuentos de nada mayor que días.

Las otras respuestas que probé eran de

  • pregunta 3054715 – LukeH, ho1 y esto. ___curious_geek
  • pregunta 6260372 – Chuck Rostance y Jani (la misma respuesta que esta pregunta)
  • pregunta 9 (!) – Dylan Hayes, Jon y Rajeshwaran SP

esta .___ respuesta de curious_geek es código en una página a la que se vinculó, que no creo que haya escrito. La respuesta de Jani es la única que usa una biblioteca externa, Time Period Library para .Net.

Todas las demás respuestas sobre todas estas preguntas parecían estar incompletas. La pregunta 9 es sobre la edad en años, y las tres respuestas son las que excedieron los años, meses y días breves y calculados. Si alguien encuentra más duplicados de esta pregunta, por favor hágamelo saber.

Cómo probé

Sencillamente: hice una interfaz

 public interface IDateDifference { void SetDates(DateTime start, DateTime end); int GetYears(); int GetMonths(); int GetDays(); } 

Para cada respuesta escribí una clase implementando esta interfaz, usando el código copiado y pegado como base. Por supuesto, tuve que adaptar funciones con diferentes firmas, etc., pero traté de hacer las ediciones mínimas para hacerlo, conservando todo el código lógico.

Escribí un montón de pruebas de NUnit en una clase genérica abstracta

 [TestFixture] public abstract class DateDifferenceTests where DDC : IDateDifference, new() 

y agregó una clase derivada vacía

 public class Rajeshwaran_S_P_Test : DateDifferenceTests { } 

al archivo de origen para cada clase IDateDifference .

NUnit es lo suficientemente inteligente como para hacer el rest.

Los exámenes

Un par de estos se escribieron por adelantado y el rest se escribieron para tratar de romper implementaciones que aparentemente funcionan.

 [TestFixture] public abstract class DateDifferenceTests where DDC : IDateDifference, new() { protected IDateDifference ddClass; [SetUp] public void Init() { ddClass = new DDC(); } [Test] public void BasicTest() { ddClass.SetDates(new DateTime(2012, 12, 1), new DateTime(2012, 12, 25)); CheckResults(0, 0, 24); } [Test] public void AlmostTwoYearsTest() { ddClass.SetDates(new DateTime(2010, 8, 29), new DateTime(2012, 8, 14)); CheckResults(1, 11, 16); } [Test] public void AlmostThreeYearsTest() { ddClass.SetDates(new DateTime(2009, 7, 29), new DateTime(2012, 7, 14)); CheckResults(2, 11, 15); } [Test] public void BornOnALeapYearTest() { ddClass.SetDates(new DateTime(2008, 2, 29), new DateTime(2009, 2, 28)); CheckControversialResults(0, 11, 30, 1, 0, 0); } [Test] public void BornOnALeapYearTest2() { ddClass.SetDates(new DateTime(2008, 2, 29), new DateTime(2009, 3, 1)); CheckControversialResults(1, 0, 0, 1, 0, 1); } [Test] public void LongMonthToLongMonth() { ddClass.SetDates(new DateTime(2010, 1, 31), new DateTime(2010, 3, 31)); CheckResults(0, 2, 0); } [Test] public void LongMonthToLongMonthPenultimateDay() { ddClass.SetDates(new DateTime(2009, 1, 31), new DateTime(2009, 3, 30)); CheckResults(0, 1, 30); } [Test] public void LongMonthToShortMonth() { ddClass.SetDates(new DateTime(2009, 8, 31), new DateTime(2009, 9, 30)); CheckControversialResults(0, 1, 0, 0, 0, 30); } [Test] public void LongMonthToPartWayThruShortMonth() { ddClass.SetDates(new DateTime(2009, 8, 31), new DateTime(2009, 9, 10)); CheckResults(0, 0, 10); } private void CheckResults(int years, int months, int days) { Assert.AreEqual(years, ddClass.GetYears()); Assert.AreEqual(months, ddClass.GetMonths()); Assert.AreEqual(days, ddClass.GetDays()); } private void CheckControversialResults(int years, int months, int days, int yearsAlt, int monthsAlt, int daysAlt) { // gives the right output but unhelpful messages bool success = ((ddClass.GetYears() == years && ddClass.GetMonths() == months && ddClass.GetDays() == days) || (ddClass.GetYears() == yearsAlt && ddClass.GetMonths() == monthsAlt && ddClass.GetDays() == daysAlt)); Assert.IsTrue(success); } } 

La mayoría de los nombres son un poco tontos y realmente no explican por qué el código puede fallar la prueba, sin embargo, al observar las dos fechas y las respuestas deberían ser suficientes para comprender la prueba.

Hay dos funciones que hacen todos los Assert , CheckResults() y CheckControversialResults() . Funcionan bien para ahorrar tipeo y dar los resultados correctos, pero desafortunadamente hacen que sea más difícil ver exactamente qué salió mal (porque el Assert en CheckControversialResults() fallará con “Esperado verdadero”, en lugar de decirle qué valor era incorrecto. cualquiera tiene una mejor manera de hacerlo (evite escribir los mismos controles cada vez, pero tenga más mensajes de error útiles) por favor hágamelo saber.

CheckControversialResults() se utiliza para un par de casos en los que parece haber dos opiniones diferentes sobre lo que es correcto. Tengo una opinión propia, pero pensé que debería ser liberal en lo que acepté aquí. La esencia de esto es decidir si un año después del 29 de febrero es el 28 de febrero o el 1 de marzo.

Estas pruebas son el quid de la cuestión, y bien podría haber errores en ellas, así que por favor comente si encuentra una que sea incorrecta. También sería bueno escuchar algunas sugerencias para otras pruebas para verificar futuras iteraciones de respuestas.

Ninguna prueba implica la hora del día; todos los DateTime son a medianoche. Incluyendo los tiempos, siempre y cuando esté claro cómo funciona el redondeo hacia arriba y hacia abajo a los días (creo que lo es), podría aparecer aún más defectos.

Los resultados

El marcador completo de resultados es el siguiente:

 ChuckRostance_Test 3 failures SSSFSSFSF Dave_Test 6 failures FFSFFFFSS Dylan_Hayes_Test 9 failures FFFFFFFFF ho1_Test 3 failures FFSSSSFSS Jani_Test 6 failures FFSFFFFSS Jon_Test 1 failure SSSSSSFSS lc_Test 2 failures SSSSSFFSS LukeH_Test 1 failure SSSSSSFSS Malu_MN_Test 1 failure SSSSSSSFS Mohammed_Ijas_Nasirudeen_Test 2 failures FSSFSSSSS pk_Test 6 failures FFFSSFFFS Rajeshwaran_S_P_Test 7 failures FFSFFSFFF ruffin_Test 3 failures FSSFSSFSS this_curious_geek_Test 2 failures FSSFSSSSS 

Pero tenga en cuenta que la solución de Jani fue en realidad correcta y pasó todas las pruebas; consulte la actualización 4 a continuación.

Las columnas están en orden alfabético del nombre de la prueba:

  • Casi tres años de prueba
  • AlmostTwoYearsTest
  • Prueba Básica
  • BornOnALeapYearTest
  • BornOnALeapYearTest2
  • LongMonthToLongMonth
  • LongMonthToLongMonthPenultimateDay
  • LongMonthToPartWayThruShortMonth
  • LongMonthToShortMonth

Tres respuestas fallaron solo 1 prueba cada una, Jon’s, LukeH’s y Manu MN’s. Tenga en cuenta que estas pruebas probablemente se escribieron específicamente para abordar los defectos en esas respuestas.

Cada prueba fue aprobada por al menos una pieza de código, lo que es ligeramente tranquilizador de que ninguna de las pruebas es errónea.

Algunas respuestas fallaron en muchas pruebas. Espero que nadie sienta que esto es una condena de los esfuerzos de ese afiche. En primer lugar, la cantidad de éxitos es bastante arbitraria ya que las pruebas no cubren uniformemente las áreas problemáticas del espacio de preguntas. En segundo lugar, esto no es un código de producción: las respuestas se publican para que las personas puedan aprender de ellas y no copiarlas exactamente en sus progtwigs. El código que no supera muchas pruebas puede tener buenas ideas. Al menos una pieza que falló en muchas pruebas tenía un pequeño error que no solucioné. Agradezco a todos los que se tomaron el tiempo para compartir su trabajo con todos los demás, por hacer que este proyecto fuera tan interesante.

Mis conclusiones

Hay tres:

  1. Los calendarios son duros. Escribí nueve pruebas, incluidas tres donde son posibles dos respuestas. Algunas de las pruebas en las que solo tuve una respuesta podrían no estar unánimemente de acuerdo. Solo pensar exactamente a qué nos referimos cuando decimos ‘1 mes después’ o ‘2 años antes’ es complicado en muchas situaciones. Y ninguno de estos códigos tuvo que lidiar con todas las complejidades de cosas como entrenar cuando los años bisiestos son. Todo usa código de biblioteca para manejar las fechas. Si te imaginas la “especificación” para decir la hora en días, semanas, meses y años escritos, hay todo tipo de cruft. Debido a que lo conocemos bastante bien desde la escuela primaria, y lo usamos todos los días, estamos ciegos a muchas de las idiosincrasias. La pregunta no es académica: varios tipos de descomposición de períodos de tiempo en años, trimestres y meses son esenciales en el software de contabilidad para bonos y otros productos financieros.

  2. Escribir el código correcto es difícil. Hubo muchos errores. En temas ligeramente más oscuros o menos populares, las posibilidades de que exista un error sin haber sido señaladas por un comentarista son mucho, mucho más altas que las de esta pregunta. Nunca debería, nunca debe copiar el código de SO en su progtwig sin entender exactamente lo que hace. El otro lado de esto es que probablemente no deberías escribir código en tu respuesta que esté listo para ser copiado y pegado, sino un pseudocódigo bastante inteligente y expresivo que permite a alguien entender la solución e implementar su propia versión (con sus propios errores) !

  3. Las pruebas unitarias son útiles. Todavía tengo intención de publicar mi propia solución cuando me acerque (¡para que alguien más descubra las suposiciones ocultas e incorrectas!) Hacer esto fue un gran ejemplo de ‘salvar los errores’ al convertirlos en pruebas unitarias para arreglar la próxima versión del código con.

Actualizar

Todo el proyecto está ahora en https://github.com/jwg4/date-difference Esto incluye mi propio bash jwg.cs , que pasa todas las pruebas que tengo actualmente, incluidas algunas nuevas que comprueban la correcta hora del día. . Siéntase libre de agregar más pruebas para romper esta y otras implementaciones o un mejor código para responder la pregunta.

Actualización 2

@MattJohnson ha agregado una implementación que utiliza NodaTime de Jon Skeet. Pasa todas las pruebas actuales.

Actualización 3

La respuesta de @ KirkWoll a la diferencia en meses entre dos fechas se ha agregado al proyecto en github. Pasa todas las pruebas actuales.

Actualización 4

@Jani señaló en un comentario que había usado su código incorrectamente. Sí sugirió métodos que contaron los años, meses y días correctamente (junto con algunos que cuentan el número total de días y meses, no los restantes), pero usé equivocadamente los incorrectos en mi código de prueba. He corregido mi envoltorio alrededor de su código y ahora pasa todas las pruebas. Ahora hay cuatro soluciones correctas, de las cuales la de Jani fue la primera. Dos bibliotecas de uso (Intenso.TimePeriod y NodaTime) y dos se escriben desde cero.

Para el cálculo de la diferencia correcta de Años / Meses / Semanas, se debe considerar el Calendario de CultureInfo :

  • año bisiesto versus años no bisiestos
  • meses con diferente cantidad de días
  • años con diferente cantidad de semanas (que varían según el primer día de la semana y la regla de la semana calendario)

La clase DateDiff de Time Period Library para .NET respeta todos estos factores:

 // ---------------------------------------------------------------------- public void DateDiffSample() { DateTime date1 = new DateTime( 2009, 11, 8, 7, 13, 59 ); Console.WriteLine( "Date1: {0}", date1 ); // > Date1: 08.11.2009 07:13:59 DateTime date2 = new DateTime( 2011, 3, 20, 19, 55, 28 ); Console.WriteLine( "Date2: {0}", date2 ); // > Date2: 20.03.2011 19:55:28 DateDiff dateDiff = new DateDiff( date1, date2 ); // differences Console.WriteLine( "DateDiff.Years: {0}", dateDiff.Years ); // > DateDiff.Years: 1 Console.WriteLine( "DateDiff.Quarters: {0}", dateDiff.Quarters ); // > DateDiff.Quarters: 5 Console.WriteLine( "DateDiff.Months: {0}", dateDiff.Months ); // > DateDiff.Months: 16 Console.WriteLine( "DateDiff.Weeks: {0}", dateDiff.Weeks ); // > DateDiff.Weeks: 70 Console.WriteLine( "DateDiff.Days: {0}", dateDiff.Days ); // > DateDiff.Days: 497 Console.WriteLine( "DateDiff.Weekdays: {0}", dateDiff.Weekdays ); // > DateDiff.Weekdays: 71 Console.WriteLine( "DateDiff.Hours: {0}", dateDiff.Hours ); // > DateDiff.Hours: 11940 Console.WriteLine( "DateDiff.Minutes: {0}", dateDiff.Minutes ); // > DateDiff.Minutes: 716441 Console.WriteLine( "DateDiff.Seconds: {0}", dateDiff.Seconds ); // > DateDiff.Seconds: 42986489 // elapsed Console.WriteLine( "DateDiff.ElapsedYears: {0}", dateDiff.ElapsedYears ); // > DateDiff.ElapsedYears: 1 Console.WriteLine( "DateDiff.ElapsedMonths: {0}", dateDiff.ElapsedMonths ); // > DateDiff.ElapsedMonths: 4 Console.WriteLine( "DateDiff.ElapsedDays: {0}", dateDiff.ElapsedDays ); // > DateDiff.ElapsedDays: 12 Console.WriteLine( "DateDiff.ElapsedHours: {0}", dateDiff.ElapsedHours ); // > DateDiff.ElapsedHours: 12 Console.WriteLine( "DateDiff.ElapsedMinutes: {0}", dateDiff.ElapsedMinutes ); // > DateDiff.ElapsedMinutes: 41 Console.WriteLine( "DateDiff.ElapsedSeconds: {0}", dateDiff.ElapsedSeconds ); // > DateDiff.ElapsedSeconds: 29 // description Console.WriteLine( "DateDiff.GetDescription(1): {0}", dateDiff.GetDescription( 1 ) ); // > DateDiff.GetDescription(1): 1 Year Console.WriteLine( "DateDiff.GetDescription(2): {0}", dateDiff.GetDescription( 2 ) ); // > DateDiff.GetDescription(2): 1 Year 4 Months Console.WriteLine( "DateDiff.GetDescription(3): {0}", dateDiff.GetDescription( 3 ) ); // > DateDiff.GetDescription(3): 1 Year 4 Months 12 Days Console.WriteLine( "DateDiff.GetDescription(4): {0}", dateDiff.GetDescription( 4 ) ); // > DateDiff.GetDescription(4): 1 Year 4 Months 12 Days 12 Hours Console.WriteLine( "DateDiff.GetDescription(5): {0}", dateDiff.GetDescription( 5 ) ); // > DateDiff.GetDescription(5): 1 Year 4 Months 12 Days 12 Hours 41 Mins Console.WriteLine( "DateDiff.GetDescription(6): {0}", dateDiff.GetDescription( 6 ) ); // > DateDiff.GetDescription(6): 1 Year 4 Months 12 Days 12 Hours 41 Mins 29 Secs } // DateDiffSample 

DateDiff también calcula la diferencia de Quarters.

Los años bisiestos y los meses desiguales realmente hacen que este sea un problema no trivial. Estoy seguro de que alguien puede encontrar una forma más eficiente, pero esta es una opción: aproximar primero el lado pequeño y ajustar (no probado):

 public static void GetDifference(DateTime date1, DateTime date2, out int Years, out int Months, out int Weeks, out int Days) { //assumes date2 is the bigger date for simplicity //years TimeSpan diff = date2 - date1; Years = diff.Days / 366; DateTime workingDate = date1.AddYears(Years); while(workingDate.AddYears(1) <= date2) { workingDate = workingDate.AddYears(1); Years++; } //months diff = date2 - workingDate; Months = diff.Days / 31; workingDate = workingDate.AddMonths(Months); while(workingDate.AddMonths(1) <= date2) { workingDate = workingDate.AddMonths(1); Months++; } //weeks and days diff = date2 - workingDate; Weeks = diff.Days / 7; //weeks always have 7 days Days = diff.Days % 7; } 

¿Qué pasa con el uso del espacio de nombres System.Data.Linq y su método SqlMethods.DateDiffMonth ?

Por ejemplo, diga:

 DateTime starDT = {01-Jul-2009 12:00:00 AM} DateTime endDT = {01-Nov-2009 12:00:00 AM} 

Entonces:

 int monthDiff = System.Data.Linq.SqlClient.SqlMethods.DateDiffMonth(startDT, endDT); 

==> 4

Hay otros métodos estáticos de DateDiff en la clase SqlMethods .

Resta dos instancias DateTime para darte un TimeSpan que tiene una propiedad Days . (Por ejemplo, en PowerShell):

 PD> ([datetime] :: today - [datetime] "2009-04-07")


 Días: 89
 Horas: 0
 Minutos: 0
 Segundos: 0
 Milisegundos: 0
 Ticks: 76896000000000
 TotalDays: 89
 TotalHours: 2136
 Total minutos: 128160
 TotalSeconds: 7689600
 Total Milisegundos: 7689600000

Convertir días en años o semanas es relativamente fácil (los días en un año podrían ser 365, 365.25, … según el contexto). Meses es mucho más difícil, porque sin una fecha base no sabes qué duración de mes se aplica.

Suponiendo que desea comenzar con su fecha base, puede restar incrementalmente mientras cuenta los primeros años (verificando los años bisiestos), luego las duraciones de los meses (indización desde el inicio del mes), luego las semanas (días restantes divididos por 7) y luego días (rest )

Hay muchos casos extremos que considerar, por ejemplo, 2005-03-01 es un año desde 2004-03-01, y desde 2004-02-29 dependiendo de lo que quiere decir con “Año”.

Si resta dos instancias de DateTime, se devolverá una instancia de TimeSpan , que representará la diferencia entre las dos fechas.

Bueno, @Jon Skeet, si no estamos preocupados por obtener más granular que días (y aún días rodando en unidades más grandes en lugar de tener un total de días), según el OP, realmente no es tan difícil en C #. Lo que hace que las matemáticas de fecha sean tan difíciles es que la cantidad de unidades en cada unidad compuesta a menudo cambia. Imagínese si cada 3 ° galón de gasolina era solo 3 cuartos, pero cada 12 era 7, excepto los viernes, cuando …

Afortunadamente, las fechas son solo un largo viaje a través de la función de entero más grande . Estas locas excepciones son enloquecedoras, a menos que hayas recorrido todo el camino a través de la unidad compuesta de demonios, cuando ya no es gran cosa. Si nació el 25/12/1900, todavía EXACTAMENTE 100 en 12/25/2000, independientemente de los años bisiestos o segundos o períodos de ahorro de luz natural que haya tenido. Tan pronto como haya recorrido los porcentajes que conforman la última unidad compuesta, volverá a la unidad. Has agregado uno y empiezas de nuevo.

Lo cual es solo decir que si estás haciendo años o meses o días, la única unidad extrañamente compuesta es el mes (de días). Si necesita tomar prestado el valor del mes para manejar un lugar donde está restando más días de los que tiene, solo necesita saber la cantidad de días del mes anterior. No hay otros valores atípicos importantes.

Y C # te lo da en System.DateTime.DaysInMonth (intYear, intMonth).

(Si su mes de Now es más pequeño que su mes Then, no hay problema. Cada año tiene 12 meses).

Y el mismo trato si vamos más granulares … solo necesitas saber cuántas (unidades pequeñas) están en la última (unidad compuesta). Una vez que haya pasado, obtendrá otro valor entero más de (unidad compuesta). Luego, resta la cantidad de unidades pequeñas que perdiste comenzando donde lo hiciste. Luego, sum cuántos de los que pasaste después de la ruptura de la unidad compuesta con tu Ahora.

Así que aquí está lo que obtuve de mi primer corte al restar dos fechas. Podría funcionar. Esperanzadamente útil.

(EDITAR: Cambió NewMonth> OldMonth check a NewMonth> = OldMonth, ya que no necesitamos pedir prestado uno si los Months son los mismos (ídem para días). Es decir, Nov 11 2011 menos Nov 9 2010 dio -1 año , 12 meses, 2 días (es decir, 2 días, pero el real se tomó prestado cuando la realeza no lo necesitó).

(EDITAR: Tuve que verificar el Mes = Mes en el que necesitábamos días prestados para restar un dteThen.Day de dteNow.Day & dteNow.Day

 private void Form1_Load(object sender, EventArgs e) { DateTime dteThen = DateTime.Parse("3/31/2010"); DateTime dteNow = DateTime.Now; int intDiffInYears = 0; int intDiffInMonths = 0; int intDiffInDays = 0; if (dteNow.Month >= dteThen.Month) { if (dteNow.Day >= dteThen.Day) { // this is a best case, easy subtraction situation intDiffInYears = dteNow.Year - dteThen.Year; intDiffInMonths = dteNow.Month - dteThen.Month; intDiffInDays = dteNow.Day - dteThen.Day; } else { // else we need to substract one from the month diff (borrow the one) // and days get wacky. // Watch for the outlier of Month = Month with DayNow < DayThen, as then we've // got to subtract one from the year diff to borrow a month and have enough // days to subtract Then from Now. if (dteNow.Month == dteThen.Month) { intDiffInYears = dteNow.Year - dteThen.Year - 1; intDiffInMonths = 11; // we borrowed a year and broke ONLY // the LAST month into subtractable days // Stay with me -- because we borrowed days from the year, not the month, // this is much different than what appears to be a similar calculation below. // We know we're a full intDiffInYears years apart PLUS eleven months. // Now we need to know how many days occurred before dteThen was done with // dteThen.Month. Then we add the number of days we've "earned" in the current // month. // // So 12/25/2009 to 12/1/2011 gives us // 11-9 = 2 years, minus one to borrow days = 1 year difference. // 1 year 11 months - 12 months = 11 months difference // (days from 12/25 to the End Of Month) + (Begin of Month to 12/1) = // (31-25) + (0+1) = // 6 + 1 = // 7 days diff // // 12/25/2009 to 12/1/2011 is 1 year, 11 months, 7 days apart. QED. int intDaysInSharedMonth = System.DateTime.DaysInMonth(dteThen.Year, dteThen.Month); intDiffInDays = intDaysInSharedMonth - dteThen.Day + dteNow.Day; } else { intDiffInYears = dteNow.Year - dteThen.Year; intDiffInMonths = dteNow.Month - dteThen.Month - 1; // So now figure out how many more days we'd need to get from dteThen's // intDiffInMonth-th month to get to the current month/day in dteNow. // That is, if we're comparing 2/8/2011 to 11/7/2011, we've got (10/8-2/8) = 8 // full months between the two dates. But then we've got to go from 10/8 to // 11/07. So that's the previous month's (October) number of days (31) minus // the number of days into the month dteThen went (8), giving the number of days // needed to get us to the end of the month previous to dteNow (23). Now we // add back the number of days that we've gone into dteNow's current month (7) // to get the total number of days we've gone since we ran the greatest integer // function on the month difference (23 to the end of the month + 7 into the // next month == 30 total days. You gotta make it through October before you // get another month, G, and it's got 31 days). int intDaysInPrevMonth = System.DateTime.DaysInMonth(dteNow.Year, (dteNow.Month - 1)); intDiffInDays = intDaysInPrevMonth - dteThen.Day + dteNow.Day; } } } else { // else dteThen.Month > dteNow.Month, and we've got to amend our year subtraction // because we haven't earned our entire year yet, and don't want an obo error. intDiffInYears = dteNow.Year - dteThen.Year - 1; // So if the dates were THEN: 6/15/1999 and NOW: 2/20/2010... // Diff in years is 2010-1999 = 11, but since we're not to 6/15 yet, it's only 10. // Diff in months is (Months in year == 12) - (Months lost between 1/1/1999 and 6/15/1999 // when dteThen's clock wasn't yet rolling == 6) = 6 months, then you add the months we // have made it into this year already. The clock's been rolling through 2/20, so two months. // Note that if the 20 in 2/20 hadn't been bigger than the 15 in 6/15, we're back to the // intDaysInPrevMonth trick from earlier. We'll do that below, too. intDiffInMonths = 12 - dteThen.Month + dteNow.Month; if (dteNow.Day >= dteThen.Day) { intDiffInDays = dteNow.Day - dteThen.Day; } else { intDiffInMonths--; // subtract the month from which we're borrowing days. // Maybe we shoulda factored this out previous to the if (dteNow.Month > dteThen.Month) // call, but I think this is more readable code. int intDaysInPrevMonth = System.DateTime.DaysInMonth(dteNow.Year, (dteNow.Month - 1)); intDiffInDays = intDaysInPrevMonth - dteThen.Day + dteNow.Day; } } this.addToBox("Years: " + intDiffInYears + " Months: " + intDiffInMonths + " Days: " + intDiffInDays); // adds results to a rich text box. } 
 DateTime dt1 = new DateTime(2009, 3, 14); DateTime dt2 = new DateTime(2008, 3, 15); int diffMonth = Math.Abs((dt2.Year - dt1.Year)*12 + dt1.Month - dt2.Month) 

Encontré esta publicación mientras buscaba resolver un problema similar. Estaba tratando de encontrar la edad de un animal en unidades de años, meses, semanas y días. Those values are then displayed in SpinEdits where the user can manually change the values to find/estimate a birth date. When my form was passed a birth date from a month with less than 31 days, the value calculated was 1 day off. I based my solution off of Ic’s answer above.

Main calculation method that is called after my form loads.

  birthDateDisplay.Text = birthDate.ToString("MM/dd/yyyy"); DateTime currentDate = DateTime.Now; Int32 numOfDays = 0; Int32 numOfWeeks = 0; Int32 numOfMonths = 0; Int32 numOfYears = 0; // changed code to follow this model http://stackoverflow.com/posts/1083990/revisions //years TimeSpan diff = currentDate - birthDate; numOfYears = diff.Days / 366; DateTime workingDate = birthDate.AddYears(numOfYears); while (workingDate.AddYears(1) <= currentDate) { workingDate = workingDate.AddYears(1); numOfYears++; } //months diff = currentDate - workingDate; numOfMonths = diff.Days / 31; workingDate = workingDate.AddMonths(numOfMonths); while (workingDate.AddMonths(1) <= currentDate) { workingDate = workingDate.AddMonths(1); numOfMonths++; } //weeks and days diff = currentDate - workingDate; numOfWeeks = diff.Days / 7; //weeks always have 7 days // if bday month is same as current month and bday day is after current day, the date is off by 1 day if(DateTime.Now.Month == birthDate.Month && DateTime.Now.Day < birthDate.Day) numOfDays = diff.Days % 7 + 1; else numOfDays = diff.Days % 7; // If the there are fewer than 31 days in the birth month, the date calculated is 1 off // Dont need to add a day for the first day of the month int daysInMonth = 0; if ((daysInMonth = DateTime.DaysInMonth(birthDate.Year, birthDate.Month)) != 31 && birthDate.Day != 1) { startDateforCalc = DateTime.Now.Date.AddDays(31 - daysInMonth); // Need to add 1 more day if it is a leap year and Feb 29th is the date if (DateTime.IsLeapYear(birthDate.Year) && birthDate.Day == 29) startDateforCalc = startDateforCalc.AddDays(1); } yearsSpinEdit.Value = numOfYears; monthsSpinEdit.Value = numOfMonths; weeksSpinEdit.Value = numOfWeeks; daysSpinEdit.Value = numOfDays; 

And then, in my spinEdit_EditValueChanged event handler, I calculate the new birth date starting from my startDateforCalc based on the values in the spin edits. (SpinEdits are constrained to only allow >=0)

 birthDate = startDateforCalc.Date.AddYears(-((Int32)yearsSpinEdit.Value)).AddMonths(-((Int32)monthsSpinEdit.Value)).AddDays(-(7 * ((Int32)weeksSpinEdit.Value) + ((Int32)daysSpinEdit.Value))); birthDateDisplay.Text = birthDate.ToString("MM/dd/yyyy"); 

I know its not the prettiest solution, but it seems to be working for me for all month lengths and years.

Days: (endDate – startDate).Days
Weeks: (endDate – startDate).Days / 7
Years: Months / 12
Months: A TimeSpan only provides Days, so use the following code to get the number of whole months between a specified start and end date. For example, the number of whole months between 01/10/2000 and 02/10/2000 is 1. The the number of whole months between 01/10/2000 and 02/09/2000 is 0.

  public int getMonths(DateTime startDate, DateTime endDate) { int months = 0; if (endDate.Month <= startDate.Month) { if (endDate.Day < startDate.Day) { months = (12 * (endDate.Year - startDate.Year - 1)) + (12 - startDate.Month + endDate.Month - 1); } else if (endDate.Month < startDate.Month) { months = (12 * (endDate.Year - startDate.Year - 1)) + (12 - startDate.Month + endDate.Month); } else // (endDate.Month == startDate.Month) && (endDate.Day >= startDate.Day) { months = (12 * (endDate.Year - startDate.Year)); } } else if (endDate.Day < startDate.Day) { months = (12 * (endDate.Year - startDate.Year)) + (endDate.Month - startDate.Month) - 1; } else // (endDate.Month > startDate.Month) && (endDate.Day >= startDate.Day) { months = (12 * (endDate.Year - startDate.Year)) + (endDate.Month - startDate.Month); } return months; } 

If you have to find the difference between originalDate and today’s date, Here is a reliable algorithm without so many condition checks.

  1. Declare a intermediateDate variable and initialize to the originalDate
  2. Find difference between years.(yearDiff)
  3. Add yearDiff to intermediateDate and check whether the value is greater than today’s date.
  4. If newly obtained intermediateDate > today’s date adjust the yearDiff and intermediateDate by one.
  5. Continue above steps for month and Days.

I have used System.Data.Linq functions to do find the year, month and day differences. Please find c# code below

  DateTime todaysDate = DateTime.Now; DateTime interimDate = originalDate; ///Find Year diff int yearDiff = System.Data.Linq.SqlClient.SqlMethods.DateDiffYear(interimDate, todaysDate); interimDate = interimDate.AddYears(yearDiff); if (interimDate > todaysDate) { yearDiff -= 1; interimDate = interimDate.AddYears(-1); } ///Find Month diff int monthDiff = System.Data.Linq.SqlClient.SqlMethods.DateDiffMonth(interimDate, todaysDate); interimDate = interimDate.AddMonths(monthDiff); if (interimDate > todaysDate) { monthDiff -= 1; interimDate = interimDate.AddMonths(-1); } ///Find Day diff int daysDiff = System.Data.Linq.SqlClient.SqlMethods.DateDiffDay(interimDate, todaysDate); 
 private void dateTimePicker1_ValueChanged(object sender, EventArgs e) { int gyear = dateTimePicker1.Value.Year; int gmonth = dateTimePicker1.Value.Month; int gday = dateTimePicker1.Value.Day; int syear = DateTime.Now.Year; int smonth = DateTime.Now.Month; int sday = DateTime.Now.Day; int difday = DateTime.DaysInMonth(syear, gmonth); agedisplay = (syear - gyear); lmonth = (smonth - gmonth); lday = (sday - gday); if (smonth < gmonth) { agedisplay = agedisplay - 1; } if (smonth == gmonth) { if (sday < (gday)) { agedisplay = agedisplay - 1; } } if (smonth < gmonth) { lmonth = (-(-smonth)+(-gmonth)+12); } if (lday < 0) { lday = difday - (-lday); lmonth = lmonth - 1; } if (smonth == gmonth && sday < gday&&gyear!=syear) { lmonth = 11; } ageDisplay.Text = Convert.ToString(agedisplay) + " Years, " + lmonth + " Months, " + lday + " Days."; } 

Use Noda Time :

 var ld1 = new LocalDate(2012, 1, 1); var ld2 = new LocalDate(2013, 12, 25); var period = Period.Between(ld1, ld2); Debug.WriteLine(period); // "P1Y11M24D" (ISO8601 format) Debug.WriteLine(period.Years); // 1 Debug.WriteLine(period.Months); // 11 Debug.WriteLine(period.Days); // 24 
 TimeSpan period = endDate.AddDays(1) - startDate; DateTime date = new DateTime(period.Ticks); int totalYears = date.Year - 1; int totalMonths = ((date.Year - 1) * 12) + date.Month - 1; int totalWeeks = (int)period.TotalDays / 7; 

date.Year – 1 because the year 0 doesn’t exist. date.Month – 1, the month 0 doesn’t exist

Use the Subtract method of the DateTime object which returns a TimeSpan

 DateTime dt1 = new DateTime(2009, 3, 14); DateTime dt2 = new DateTime(2008, 3, 15); TimeSpan ts = dt1.Subtract(dt2); Double days = ts.TotalDays; Double hours = ts.TotalHours; Double years = ts.TotalDays / 365; 

Based on Joaquim’s answer, but fixing the calculation when end date month is less than start date month, and adding code to handle end date before start date:

  public static class GeneralHelper { public static int GetYears(DateTime startDate, DateTime endDate) { if (endDate < startDate) return -GetYears(endDate, startDate); int years = (endDate.Year - startDate.Year); if (endDate.Year == startDate.Year) return years; if (endDate.Month < startDate.Month) return years - 1; if (endDate.Month == startDate.Month && endDate.Day < startDate.Day) return years - 1; return years; } public static int GetMonths(DateTime startDate, DateTime endDate) { if (startDate > endDate) return -GetMonths(endDate, startDate); int months = 12 * GetYears(startDate, endDate); if (endDate.Month > startDate.Month) months = months + endDate.Month - startDate.Month; else months = 12 - startDate.Month + endDate.Month; if (endDate.Day < startDate.Day) months = months - 1; return months; } } [TestClass()] public class GeneralHelperTest { [TestMethod] public void GetYearsTest() { Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2000, 12, 31))); Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2001, 4, 4))); Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2001, 5, 4))); Assert.AreEqual(1, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2001, 5, 5))); Assert.AreEqual(1, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2001, 12, 31))); Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2000, 12, 31), new DateTime(2000, 5, 5))); Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2001, 4, 4), new DateTime(2000, 5, 5))); Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2001, 5, 4), new DateTime(2000, 5, 5))); Assert.AreEqual(-1, GeneralHelper.GetYears(new DateTime(2001, 5, 5), new DateTime(2000, 5, 5))); Assert.AreEqual(-1, GeneralHelper.GetYears(new DateTime(2001, 12, 31), new DateTime(2000, 5, 5))); } [TestMethod] public void GetMonthsTest() { Assert.AreEqual(0, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2000, 6, 4))); Assert.AreEqual(1, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2000, 6, 5))); Assert.AreEqual(1, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2000, 6, 6))); Assert.AreEqual(11, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2001, 5, 4))); Assert.AreEqual(12, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2001, 5, 5))); Assert.AreEqual(13, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2001, 6, 6))); Assert.AreEqual(0, GeneralHelper.GetMonths(new DateTime(2000, 6, 4), new DateTime(2000, 5, 5))); Assert.AreEqual(-1, GeneralHelper.GetMonths(new DateTime(2000, 6, 5), new DateTime(2000, 5, 5))); Assert.AreEqual(-1, GeneralHelper.GetMonths(new DateTime(2000, 6, 6), new DateTime(2000, 5, 5))); Assert.AreEqual(-11, GeneralHelper.GetMonths(new DateTime(2001, 5, 4), new DateTime(2000, 5, 5))); Assert.AreEqual(-12, GeneralHelper.GetMonths(new DateTime(2001, 5, 5), new DateTime(2000, 5, 5))); Assert.AreEqual(-13, GeneralHelper.GetMonths(new DateTime(2001, 6, 6), new DateTime(2000, 5, 5))); } } 

EDIT No that still doesn't work. It fails this test:

 Assert.AreEqual(24, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2003, 5, 5))); 

Expected:<24>. Actual:<12>

I was trying to find a clear answer for Years, Months and Days, and I didn’t find anything clear, If you are still looking check this method:

  public static string GetDifference(DateTime d1, DateTime d2) { int[] monthDay = new int[12] { 31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; DateTime fromDate; DateTime toDate; int year; int month; int day; int increment = 0; if (d1 > d2) { fromDate = d2; toDate = d1; } else { fromDate = d1; toDate = d2; } // Calculating Days if (fromDate.Day > toDate.Day) { increment = monthDay[fromDate.Month - 1]; } if (increment == -1) { if (DateTime.IsLeapYear(fromDate.Year)) { increment = 29; } else { increment = 28; } } if (increment != 0) { day = (toDate.Day + increment) - fromDate.Day; increment = 1; } else { day = toDate.Day - fromDate.Day; } // Month Calculation if ((fromDate.Month + increment) > toDate.Month) { month = (toDate.Month + 12) - (fromDate.Month + increment); increment = 1; } else { month = (toDate.Month) - (fromDate.Month + increment); increment = 0; } // Year Calculation year = toDate.Year - (fromDate.Year + increment); return year + " years " + month + " months " + day + " days"; } 

I have below solution which works correctly for me(After doing some Test cases). Extra Test cases are acceptable.

 public class DateDiffernce { private int m_nDays = -1; private int m_nWeek; private int m_nMonth = -1; private int m_nYear; public int Days { get { return m_nDays; } } public int Weeks { get { return m_nWeek; } } public int Months { get { return m_nMonth; } } public int Years { get { return m_nYear; } } public void GetDifferenceBetwwenTwoDate(DateTime objDateTimeFromDate, DateTime objDateTimeToDate) { if (objDateTimeFromDate.Date > objDateTimeToDate.Date) { DateTime objDateTimeTemp = objDateTimeFromDate; objDateTimeFromDate = objDateTimeToDate; objDateTimeToDate = objDateTimeTemp; } if (objDateTimeFromDate == objDateTimeToDate) { //textBoxDifferenceDays.Text = " Same dates"; //textBoxAllDifference.Text = " Same dates"; return; } // If From Date's Day is bigger than borrow days from previous month // & then subtract. if (objDateTimeFromDate.Day > objDateTimeToDate.Day) { objDateTimeToDate = objDateTimeToDate.AddMonths(-1); int nMonthDays = DateTime.DaysInMonth(objDateTimeToDate.Year, objDateTimeToDate.Month); m_nDays = objDateTimeToDate.Day + nMonthDays - objDateTimeFromDate.Day; } // If From Date's Month is bigger than borrow 12 Month // & then subtract. if (objDateTimeFromDate.Month > objDateTimeToDate.Month) { objDateTimeToDate = objDateTimeToDate.AddYears(-1); m_nMonth = objDateTimeToDate.Month + 12 - objDateTimeFromDate.Month; } //Below are best cases - simple subtraction if (m_nDays == -1) { m_nDays = objDateTimeToDate.Day - objDateTimeFromDate.Day; } if (m_nMonth == -1) { m_nMonth = objDateTimeToDate.Month - objDateTimeFromDate.Month; } m_nYear = objDateTimeToDate.Year - objDateTimeFromDate.Year; m_nWeek = m_nDays / 7; m_nDays = m_nDays % 7; } } 
  Console.WriteLine("Enter your Date of Birth to Know your Current age in DD/MM/YY Format"); string str = Console.ReadLine(); DateTime dt1 = DateTime.Parse(str); DateTime dt2 = DateTime.Parse("10/06/2012"); int result = (dt2 - dt1).Days; result = result / 365; Console.WriteLine("Your Current age is {0} years.",result); 
 DateTime startTime = DateTime.Now; DateTime endTime = DateTime.Now.AddSeconds( 75 ); TimeSpan span = endTime.Subtract ( startTime ); Console.WriteLine( "Time Difference (seconds): " + span.Seconds ); Console.WriteLine( "Time Difference (minutes): " + span.Minutes ); Console.WriteLine( "Time Difference (hours): " + span.Hours ); Console.WriteLine( "Time Difference (days): " + span.Days ); 

Salida:

 Time Difference (seconds): 15 Time Difference (minutes): 1 Time Difference (hours): 0 Time Difference (days): 0