2013-04-11 10 views
16

Ho una classe con 2 proprietà data: FirstDay e LastDay. LastDay è annullabile. Vorrei generare una stringa nel formato "x year(s) y day(s)". Se gli anni totali sono inferiori a 1, vorrei omettere la sezione dell'anno. Se i giorni totali sono inferiori a 1, vorrei omettere la sezione del giorno. Se uno o due anni sono 0, dovrebbero dire rispettivamente "giorno/anno", anziché "giorni/anni".Formato A TimeSpan con anni

Esempi:
2,2 anni:                           "2 anni 73 giorni"
1,002,738 mila anni:       "1 anno 1 giorno"
0,2 anni :                           "73 giorni"
2 anni:                                 "2 anni"

Quello che io ho le opere, ma è long:

private const decimal DaysInAYear = 365.242M; 

public string LengthInYearsAndDays 
{ 
    get 
    { 
     var lastDay = this.LastDay ?? DateTime.Today; 
     var lengthValue = lastDay - this.FirstDay; 

     var builder = new StringBuilder(); 

     var totalDays = (decimal)lengthValue.TotalDays; 
     var totalYears = totalDays/DaysInAYear; 
     var years = (int)Math.Floor(totalYears); 

     totalDays -= (years * DaysInAYear); 
     var days = (int)Math.Floor(totalDays); 

     Func<int, string> sIfPlural = value => 
      value > 1 ? "s" : string.Empty; 

     if (years > 0) 
     { 
      builder.AppendFormat(
       CultureInfo.InvariantCulture, 
       "{0} year{1}", 
       years, 
       sIfPlural(years)); 

      if (days > 0) 
      { 
       builder.Append(" "); 
      } 
     } 

     if (days > 0) 
     { 
      builder.AppendFormat(
       CultureInfo.InvariantCulture, 
       "{0} day{1}", 
       days, 
       sIfPlural(days)); 
     } 

     var length = builder.ToString(); 
     return length; 
    } 
} 

Esiste un modo più conciso per farlo (ma comunque leggibile)?

+2

http://codereview.stackexchange.com/ – Kashif

risposta

42

A TimeSpan non ha un concetto ragionevole di "anni" perché dipende dal punto iniziale e finale. (Mesi è simile - quanti mesi ci sono in 29 giorni Beh, dipende ...?)

Per dare una spina spudorato, il mio progetto Noda Time rende davvero semplice però:

using System; 
using NodaTime; 

public class Test 
{ 
    static void Main(string[] args) 
    { 
     LocalDate start = new LocalDate(2010, 6, 19); 
     LocalDate end = new LocalDate(2013, 4, 11); 
     Period period = Period.Between(start, end, 
             PeriodUnits.Years | PeriodUnits.Days); 

     Console.WriteLine("Between {0} and {1} are {2} years and {3} days", 
          start, end, period.Years, period.Days); 
    } 
} 

uscita:

Between 19 June 2010 and 11 April 2013 are 2 years and 296 days 
+13

E puoi sicuramente fidarti delle competenze di Jon su [times] (http://stackoverflow.com/a/6841479/211627) e [date] (http://msmvps.com/blogs/jon_skeet/archive/2010/ 12/01/the-joys-of-date-time-arithmetic.aspx) – JDB

+4

ugh, libreria extra di terze parti - ma la libreria di Jon Skeet è piuttosto convincente. – tofutim

+0

questo calcola anche l'anno bisestile vero ??? – Ali

1

Non lo farei con lo TimeSpan. La matematica delle date diventa complicata non appena passi oltre i giorni perché il numero di giorni in un mese e giorni in un anno non è più costante. È probabile che il motivo TimeSpan non contenga le proprietà per Years e Months. Vorrei invece determinare il numero di anni/mesi/giorni, ecc. Tra i due valori DateTime e visualizzare i risultati di conseguenza.

+1

Detto questo, sembra che l'OP abbia risolto un compromesso sufficiente (per i suoi scopi): 365,242 milioni di giorni all'anno. – JDB

+1

Non ci sono 365.242 giorni in un anno.Alcuni anni hanno 365 giorni, altri 366. Ci sono 365.242 in un anno _average_, che non funzionerà se si confrontano due date specifiche. Se tutto ciò che era disponibile era il numero di giorni (il che può fare il bastoncino 'TimeSpan'), allora sarebbe un _estimate_ decente, ma in certi casi potrebbe essere spento di un giorno. –

+1

Sono d'accordo con te, sto solo dicendo che per un progetto personale e non pubblico, la precisione può (legittimamente) prendere un posto indietro per convenienza. – JDB

5
public string GetAgeText(DateTime birthDate) 
{ 
     const double ApproxDaysPerMonth = 30.4375; 
     const double ApproxDaysPerYear = 365.25; 

     /* 
     The above are the average days per month/year over a normal 4 year period 
     We use these approximations as they are more accurate for the next century or so 
     After that you may want to switch over to these 400 year approximations 

      ApproxDaysPerMonth = 30.436875 
      ApproxDaysPerYear = 365.2425 

      How to get theese numbers: 
      The are 365 days in a year, unless it is a leepyear. 
      Leepyear is every forth year if Year % 4 = 0 
      unless year % 100 == 1 
      unless if year % 400 == 0 then it is a leep year. 

      This gives us 97 leep years in 400 years. 
      So 400 * 365 + 97 = 146097 days. 
      146097/400  = 365.2425 
      146097/400/12 = 30,436875 

     Due to the nature of the leap year calculation, on this side of the year 2100 
     you can assume every 4th year is a leap year and use the other approximatiotions 

     */ 
    //Calculate the span in days 
    int iDays = (DateTime.Now - birthDate).Days; 

    //Calculate years as an integer division 
    int iYear = (int)(iDays/ApproxDaysPerYear); 

    //Decrease remaing days 
    iDays -= (int)(iYear * ApproxDaysPerYear); 

    //Calculate months as an integer division 
    int iMonths = (int)(iDays/ApproxDaysPerMonth); 

    //Decrease remaing days 
    iDays -= (int)(iMonths * ApproxDaysPerMonth); 

    //Return the result as an string 
    return string.Format("{0} years, {1} months, {2} days", iYear, iMonths, iDays); 
} 
0

penso che questo dovrebbe funzionare:

public static int DiffYears(DateTime dateValue1, DateTime dateValue2) 
{ 
    var intToCompare1 = Convert.ToInt32(dateValue1.ToString("yyyyMMdd")); 
    var intToCompare2 = Convert.ToInt32(dateValue2.ToString("yyyyMMdd")); 
    return (intToCompare2 - intToCompare1)/10000; 
} 
+0

Qui rendiamo il mese e il giorno qui per la conversione in 'int', ma poi dividiamo per diecimila piatti - convertiti in un Int. Questo è quasi equivalente a fare 'dateValue1.Year - dateValue2.year' in termini di accuratezza. Il risultato di questo sarebbe un numero singolo che indica come gli anni sono passati completamente indipendentemente da eventuali 354 giorni finali finali. Semanticamente, anche questo è molto dubbio. –

0
Public Function TimeYMDBetween(StartDate As DateTime, EndDate As DateTime) As String 
    Dim Years As Integer = EndDate.Year - StartDate.Year 
    Dim Months As Integer = EndDate.Month - StartDate.Month 
    Dim Days As Integer = EndDate.Day - StartDate.Day 
    Dim DaysLastMonth As Integer 

    'figure out how many days were in last month 
    If EndDate.Month = 1 Then 
     DaysLastMonth = DateTime.DaysInMonth(EndDate.Year - 1, 12) 
    Else 
     DaysLastMonth = DateTime.DaysInMonth(EndDate.Year, EndDate.Month - 1) 
    End If 

    'adjust for negative days 
    If Days < 0 Then 
     Months = Months - 1 
     Days = Days + DaysLastMonth 'borrowing from last month 
    End If 

    'adjust for negative Months 
    If Months < 0 Then 'startdate hasn't happend this year yet 
     Years = Years - 1 
     Months = Months + 12 
    End If 

    Return Years.ToString() + " Years, " + Months.ToString() + " Months and " + Days.ToString() + " Days" 

End Function 
+0

Sarebbe utile spiegare perché questo codice funziona. Dare maggiori dettagli è sempre una buona idea quando rispondi alle domande. –