Объект реального времени с. Годами и. Месяцами - PullRequest
23 голосов
/ 16 декабря 2009

Рассмотрим следующие 2 сценария: Сценарий 1). Сегодня 1 мая 2012 года и сценарий 2). Сегодня 1 сентября 2012 года.

Теперь, давайте посмотрим, что мы напишем на нашей веб-странице следующее о комментарии, оставленном кем-то: «Этот комментарий был написан 3 месяца и 12 дней назад». Количество дней в обоих этих сценариях ВСЕГДА будет разным, даже если утверждение точно такое же. В сценарии 1 «3 месяца и 12 дней» будут равны 102 days. Однако в сценарии 2 «3 месяца и 12 дней» будут 104 days!

Теперь, чтобы подчеркнуть мою точку зрения, давайте рассмотрим другой пример и скажем, что кто-то оставил комментарий на нашем сайте 30 января 2013 года, а сегодня - 10 марта 2013 года. Нашему реальному объекту TimeSpan нужно знать эту относительную дату, и может выяснить следующее:

  • То, что в марте 10 дней,
  • То, что в январе 1 день (считая с 30-го по 31-е).
  • То, что месяц февраль - это один месяц, независимо от того, сколько дней в нем (даже если это 28 дней).

Итак, это будет означать 10 дней + 1 день + 1 месяц, что означает This comment was posted 1 Month and 11 Days ago.

Теперь, если вы используете объект TimeSpan в стиле MS (или любой объект TimeSpan на любом языке), он даст вам количество дней с 30 января по 10 марта (39 дней), и поскольку объект TimeSpan не сохранить относительную дату (основную / начальную дату, которую мы вычли, чтобы получить интервал времени), если вы спросите ее, сколько месяцев и дней прошло, то будет предположить, что в одном месяце 30 дней или, что еще хуже, среднее значение, которое больше 30 дней и верните остаток в днях, поэтому, чтобы получить 39 дней, вам сообщат, что прошло 1 месяц и 9 дней, и вы получите сообщение This comment was posted 1 Month and 9 Days ago. Помните, что оба этих сценария имеют одинаковую дату начала и одну и ту же текущую / конечную дату, да, объект Microsoft TimeSpan, не позволяющий нам сказать, что должен рассматриваться месяц февраль 2013, дал нам совершенно другой TimeSpan, от целых 2 дня. По сути, он солгал нам.

Проблема в том, что люди поверят этому, и кто знает, какие у них могут быть представления, как могут измениться их представления о прошлом, и какие решения и жизненный выбор они могут принять, пытаясь реконструировать события в прошлом в своем собственном уме. никогда не замечая и не понимая недостаток и присущую ему неспособность представлять время, столь распространенное сегодня повсюду. Они не поймут, что языки программирования не осознают (или не заботятся) о том, что в прошлом месяце было 31 день, а не 30, 29 или 28 - или наоборот, и что это складывается при увеличении TimeSpan.

Эта проблема лежит в основе этого поста. Я понимаю, что большинство людей не будет заботиться об этой разнице (но будьте уверены, что некоторые из нас понимают, и не могут иметь этого на своих плечах), и если это вас не беспокоит, это нормально. Хотелось бы, чтобы это меня не беспокоило, я бы сэкономил себе время, стресс и разочарование. Если это не мешает, вы можете использовать функцию для эффективного текстового отображения относительного времени (настраиваемое от 1 до 6 узлов от секунд до лет) вместо того, чтобы использовать его, как правило, с незначительной точностью, которую оно обеспечивает.

К моему разочарованию, я заметил, что нет реального объекта временного интервала, если вы получите временной интервал и сделаете .years или .months, вы ничего не получите, вы получите только .days и ниже, потому что Объект timeSpan не содержит ничего, чтобы сказать ему, в каком месяце или году был создан timeSpan. Поэтому он никогда не узнает, сколько месяцев прошло, так как дни каждого месяца меняются в течение года и даже более в високосный год.

В ответ на это я опубликую разработанную мной функцию, чтобы получать ТОЧНЫЕ показания и иметь возможность возвращать подобные вещи на моей веб-странице ASP.NET ...

Отправлено 4 года, 3 месяца, 14 дней, 15 часов, 18 минут и 24 секунды назад

Я полагал, что будет & hellip;

timeSpan.GetActualNumberOf[Months/Days/Hours/etc] (базовая дата должна быть указана, конечно)

* * 1 042 & hellip; метод type для этого типа данных, но его не было.

Все, что вам действительно нужно сделать, - это создать еще одно свойство для объекта timeSpan, чтобы присвоить ему базовую дату, на которую была рассчитана разница, тогда приведенная выше симпатичная строка будет довольно легко вычислена, и .year & .month будет существовать!

ОБНОВЛЕНИЕ: Я значительно расширил и обновил свой официальный ответ и детали использования кода в своем ответе ниже, 100% рабочий ответ и код (полностью), точные и точные относительные время / даты, без приближений - спасибо.

Ответы [ 8 ]

25 голосов
/ 18 декабря 2009

Вот как можно добавить некоторые методы расширения для этого с помощью C #, используя средние значения:

public static class TimeSpanExtensions
{
    public static int GetYears(this TimeSpan timespan)
    {
        return (int)(timespan.Days/365.2425);
    }
    public static int GetMonths(this TimeSpan timespan)
    {
        return (int)(timespan.Days/30.436875);
    }
}
10 голосов
/ 18 декабря 2009

То, что вы ищете, действительно не то, что представляет TimeSpan. TimeSpan представляет интервал в виде количества тиков, без учета базы DateTime или Calendar.

Новый тип DateDifference может иметь здесь больше смысла, так как метод конструктора или фабрики принимает основу DateTime, цель DateTime и, необязательно, Calendar (по умолчанию для CultureInfo.CurrentCulture), с которой вычислить различные компоненты разницы (годы, месяцы и т. д.)

РЕДАКТИРОВАТЬ: Мне кажется, Время Нода может иметь инструменты, необходимые для этого & mdash; Period класс «[r] представляет период времени, выраженный в хронологических терминах человека: часы, дни, недели, месяцы и т. д.», и в частности Period.Between(then, now, PeriodUnits.AllUnits) представляется точным расчетом вы просите & mdash; но это обязательно намного более сложный класс, чем TimeSpan. Страница Key Concepts на вики-сайте Noda Time объясняет, как «люди портят время»:

Оставляя в стороне хитрые кусочки астрономии и относительности, человечество по-прежнему трудно договариваться. Если бы мы все использовали тики из Unix эпоха, чтобы говорить о времени, не было бы необходимости в такой библиотеке, как Время Нода.

Но нет, нам нравится говорить годами, месяцами, днями, неделями - и для некоторых причина, по которой нам нравится, что 12:00 (что смущает до 13:00) примерно время, когда солнце является самым высоким ... таким образом, у нас есть часовых поясов .

Не только это, но мы не все согласны с тем, сколько месяцев. Разные цивилизации придумали разные способы расщепления до года, и разные цифры за годы для начала. Эти календарные системы .

5 голосов
/ 24 декабря 2009

Вот основной ответ с кодом, обратите внимание, что вы можете получить любое число с точностью до даты / времени, секунд и минут или секунд, минут и дней, где угодно, вплоть до лет (которые будут содержать 6 частей / сегментов). Если вы укажете два верхних и ему больше года, он вернет «1 год и 3 месяца назад» и не вернет остаток, потому что вы запросили два сегмента. если ему всего несколько часов, то он вернет только «2 часа и 1 минуту назад». Конечно, те же правила применяются, если вы укажете 1, 2, 3, 4, 5 или 6 сегментов (максимум равен 6, потому что секунды, минуты, часы, дни, месяцы, годы составляют только 6 типов). Это также исправит проблемы грамматики, такие как «минуты» против «минуты», в зависимости от того, равна ли она 1 минуте или более, то же самое для всех типов, и сгенерированная «строка» всегда будет грамматически правильной.

Вот несколько примеров использования: bAllowSegments указывает, сколько сегментов показывать ... т.е.: если 3, то возвращаемая строка будет (в качестве примера) ... "3 years, 2 months and 13 days" (не будет включать часы, минуты и секунды, поскольку возвращаются первые 3 категории времени) если, однако, дата была более новой, например, несколько дней назад, при указании тех же сегментов (3) вместо этого будет возвращено "4 days, 1 hour and 13 minutes ago", поэтому все учитывается!

если bAllowSegments равен 2, он вернет "3 years and 2 months", а если 6 (максимальное значение) вернет "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds", но следует напомнить, что он будет NEVER RETURN примерно так "0 years, 0 months, 0 days, 3 hours, 2 minutes and 13 seconds ago", поскольку он понимает, что данные о дате отсутствуют в верхних 3 сегментах и ​​игнорирует их, даже если вы укажете 6 сегментов, так что не волнуйтесь :). Конечно, если в нем есть сегмент с 0, он примет это во внимание при формировании строки и будет отображаться как "3 days and 4 seconds ago", игнорируя часть «0 часов»! Наслаждайтесь и, пожалуйста, комментируйте, если хотите.

 Public Function RealTimeUntilNow(ByVal dt As DateTime, Optional ByVal bAllowSegments As Byte = 2) As String
  ' bAllowSegments identifies how many segments to show... ie: if 3, then return string would be (as an example)...
  ' "3 years, 2 months and 13 days" the top 3 time categories are returned, if bAllowSegments is 2 it would return
  ' "3 years and 2 months" and if 6 (maximum value) would return "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds"
  Dim rYears, rMonths, rDays, rHours, rMinutes, rSeconds As Int16
  Dim dtNow = DateTime.Now
  Dim daysInBaseMonth = Date.DaysInMonth(dt.Year, dt.Month)

  rYears = dtNow.Year - dt.Year
  rMonths = dtNow.Month - dt.Month
  If rMonths < 0 Then rMonths += 12 : rYears -= 1 ' add 1 year to months, and remove 1 year from years.
  rDays = dtNow.Day - dt.Day
  If rDays < 0 Then rDays += daysInBaseMonth : rMonths -= 1
  rHours = dtNow.Hour - dt.Hour
  If rHours < 0 Then rHours += 24 : rDays -= 1
  rMinutes = dtNow.Minute - dt.Minute
  If rMinutes < 0 Then rMinutes += 60 : rHours -= 1
  rSeconds = dtNow.Second - dt.Second
  If rSeconds < 0 Then rSeconds += 60 : rMinutes -= 1

  ' this is the display functionality
  Dim sb As StringBuilder = New StringBuilder()
  Dim iSegmentsAdded As Int16 = 0

  If rYears > 0 Then sb.Append(rYears) : sb.Append(" year" & If(rYears <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rMonths > 0 Then sb.AppendFormat(rMonths) : sb.Append(" month" & If(rMonths <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rDays > 0 Then sb.Append(rDays) : sb.Append(" day" & If(rDays <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rHours > 0 Then sb.Append(rHours) : sb.Append(" hour" & If(rHours <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rMinutes > 0 Then sb.Append(rMinutes) : sb.Append(" minute" & If(rMinutes <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rSeconds > 0 Then sb.Append(rSeconds) : sb.Append(" second" & If(rSeconds <> 1, "s", "") & "") : iSegmentsAdded += 1

parseAndReturn:

  ' if the string is entirely empty, that means it was just posted so its less than a second ago, and an empty string getting passed will cause an error
  ' so we construct our own meaningful string which will still fit into the "Posted * ago " syntax...

  If sb.ToString = "" Then sb.Append("less than 1 second")

  Return ReplaceLast(sb.ToString.TrimEnd(" ", ",").ToString, ",", " and")

 End Function

Конечно, вам понадобится функция «ReplaceLast», которая принимает исходную строку, аргумент, указывающий, что нужно заменить, и другой аргумент, указывающий, чем вы хотите заменить его, и он заменяет только последний случай. этой строки ... я включил свой, если у вас его нет или вы не хотите его реализовывать, так что вот, он будет работать "как есть", без изменений. Я знаю, что функция reverseit больше не нужна (существует в .net), но функции ReplaceLast и ReverseIt перенесены с дней до.net, поэтому, пожалуйста, извините, как это может выглядеть устаревшим (все еще работает на 100%, используя Их уже более десяти лет, можно гарантировать, что они не содержат ошибок) ... :). Кроме того, если вы используете VB6, вы можете использовать StrReverse (оборачивая его вокруг строки, расширенной с помощью метода расширения .ReverseIt) вместо использования функции ReverseIt () (предоставляется в качестве метода расширения). Таким образом, вместо выполнения sReplacable.ReverseIt, вы должны выполнить StrReverse (sReplacable), так как StrReverse () является встроенной функцией VB6 (и делает то же самое, переворачивает заданную строку и больше ничего не делает). Если вы используете StrReverse () вместо моей общей функции ReverseIt, не стесняйтесь удалять функцию / расширение ReverseIt. Функция StrReverse () должна быть доступна в .NET, если вы импортируете устаревшую библиотеку ms-visualbasic-dll. Не имеет значения в любом случае, я написал ReverseIt () еще до того, как узнал, что функция StrReverse () существовала, и использовал ее с тех пор по привычке (нет реальной причины использовать мой в отличие от встроенной универсальной функции). StrReverse) - на самом деле, я уверен, что StrReverse (или аналогичная, более новая, специфичная для .NET версия функции обращения строк) будет написана для большей эффективности :). веселит.

<Extension()> _ 
Public Function ReplaceLast(ByVal sReplacable As String, ByVal sReplaceWhat As String, ByVal sReplaceWith As String) As String 
    ' let empty string arguments run, incase we dont know if we are sending and empty string or not. 
    sReplacable = sReplacable.ReverseIt 
    sReplacable = Replace(sReplacable, sReplaceWhat.ReverseIt, sReplaceWith.ReverseIt, , 1) ' only does first item on reversed version! 
    Return sReplacable.ReverseIt.ToString 
End Function 

<Extension()> _ 
Public Function ReverseIt(ByVal strS As String, Optional ByVal n As Integer = -1) As String 
    Dim strTempX As String = "", intI As Integer 

    If n > strS.Length Or n = -1 Then n = strS.Length 

    For intI = n To 1 Step -1 
        strTempX = strTempX + Mid(strS, intI, 1) 
    Next intI 

    ReverseIt = strTempX + Right(strS, Len(strS) - n) 

End Function 
4 голосов
/ 10 июля 2015

Ну, лучше поздно, чем ничего, я полагаю;)

Функция C #, дающая все

А это моя модифицированная версия:

private string GetElapsedTime(DateTime from_date, DateTime to_date) {
int years;
int months;
int days;
int hours;
int minutes;
int seconds;
int milliseconds;

//------------------
// Handle the years.
//------------------
years = to_date.Year - from_date.Year;

//------------------------
// See if we went too far.
//------------------------
DateTime test_date = from_date.AddMonths(12 * years);

if (test_date > to_date)
{
    years--;
    test_date = from_date.AddMonths(12 * years);
}

//--------------------------------
// Add months until we go too far.
//--------------------------------
months = 0;

while (test_date <= to_date)
{
    months++;
    test_date = from_date.AddMonths(12 * years + months);
}

months--;

//------------------------------------------------------------------
// Subtract to see how many more days, hours, minutes, etc. we need.
//------------------------------------------------------------------
from_date = from_date.AddMonths(12 * years + months);
TimeSpan remainder = to_date - from_date;
days = remainder.Days;
hours = remainder.Hours;
minutes = remainder.Minutes;
seconds = remainder.Seconds;
milliseconds = remainder.Milliseconds;

return (years > 0 ? years.ToString() + " years " : "") +
       (months > 0 ? months.ToString() + " months " : "") +
       (days > 0 ? days.ToString() + " days " : "") +
       (hours > 0 ? hours.ToString() + " hours " : "") +
       (minutes > 0 ? minutes.ToString() + " minutes " : "");}
3 голосов
/ 16 декабря 2009

Я бы сказал, что текущий TimeSpan - это объект реального времени, т. Е. Промежуток времени между 1 января 2008 года, 1:31 утра и 3 февраля 2008 года в 6:45 утра совпадает с количеством времени между 5 февраля 2008 г. в 13:45 и 9 марта 2008 г. в 6:59 вечера. На самом деле вы ищете разницу между двумя датами.

Что касается .MakeMagicHappen.gimmeSomethingPretty.surelyMShasThoughtAboutThisDilema для удовлетворения конкретных потребностей вашей системы, именно поэтому люди нанимают вас как программиста. Если используемая вами среда делает абсолютно все, ваша компания сможет нажать одну кнопку, и их система будет полностью сформирована, и вы окажетесь на линии безработицы вместе с остальными программистами.

2 голосов
/ 15 октября 2014

Используя .Net 4.5 и класс CultureInfo, можно добавить месяцы и годы к данной дате.

DateTime datetime = DateTime.UtcNow;
int years = 15;
int months = 7;

DateTime yearsAgo = CultureInfo.InvariantCulture.Calendar.AddYears(datetime, -years);
DateTime monthsInFuture = CultureInfo.InvariantCulture.Calendar.AddMonths(datetime, months);

Поскольку набирается много текста, я предпочитаю создавать методы расширения:

public static DateTime AddYears(this DateTime datetime, int years)
{
    return CultureInfo.InvariantCulture.Calendar.AddYears(datetime, years);
}

public static DateTime AddMonths(this DateTime datetime, int months)
{
    return CultureInfo.InvariantCulture.Calendar.AddMonths(datetime, months);
}

DateTime yearsAgo = datetime.AddYears(-years);
DateTime monthsInFuture = datetime.AddMonths(months);
1 голос
/ 23 мая 2011

Я полагаю, что следующий метод довольно надежный и простой, поскольку он основан на вычислении даты каркаса и возвращает читаемые строки прошедшего времени, такие как Facebook. Извините за маленькие португальские слова и множественное число, в моем случае это было необходимо.

public static string ElapsedTime(DateTime dtEvent)
{
    TimeSpan TS = DateTime.Now - dtEvent;

    int intYears = TS.Days / 365;
    int intMonths = TS.Days / 30;
    int intDays = TS.Days;
    int intHours = TS.Hours;
    int intMinutes = TS.Minutes;
    int intSeconds = TS.Seconds;

    if (intYears > 0) return String.Format("há {0} {1}", intYears, (intYears == 1) ? "ano" : "anos");
    else if (intMonths > 0) return String.Format("há {0} {1}", intMonths, (intMonths == 1) ? "mês" : "meses");
    else if (intDays > 0) return String.Format("há {0} {1}", intDays, (intDays == 1) ? "dia" : "dias");
    else if (intHours > 0) return String.Format("há ± {0} {1}", intHours, (intHours == 1) ? "hora" : "horas");
    else if (intMinutes > 0) return String.Format("há ± {0} {1}", intMinutes, (intMinutes == 1) ? "minuto" : "minutos");
    else if (intSeconds > 0) return String.Format("há ± {0} {1}", intSeconds, (intSeconds == 1) ? "segundo" : "segundos");
    else
    {
        return String.Format("em {0} às {1}", dtEvent.ToShortDateString(), dtEvent.ToShortTimeString());
    }
}
0 голосов
/ 20 мая 2019

Я взял принятый ответ и преобразовал его из VB.Net в C #, а также сделал несколько изменений / улучшений. Я избавился от обращения строк, которые использовались для замены последнего экземпляра строки, и использовал метод расширения, который более непосредственно находит и заменяет последний экземпляр строки.

Пример вызова метода:

PeriodBetween(#2/28/2011#, DateTime.UtcNow, 6)

Основной метод:

public static string PeriodBetween(DateTime then, DateTime now, byte numberOfPeriodUnits = 2)
{
    // Translated from VB.Net to C# from: https://stackoverflow.com/a/1956265

    // numberOfPeriodUnits identifies how many time period units to show.
    // If numberOfPeriodUnits = 3, function would return:
    //      "3 years, 2 months and 13 days"
    // If numberOfPeriodUnits = 2, function would return:
    //      "3 years and 2 months"
    // If numberOfPeriodUnits = 6, (maximum value), function would return:
    //      "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds"

    if (numberOfPeriodUnits > 6 || numberOfPeriodUnits < 1)
    {
        throw new ArgumentOutOfRangeException($"Parameter [{nameof(numberOfPeriodUnits)}] is out of bounds. Valid range is 1 to 6.");
    }

    short Years = 0;
    short Months = 0;
    short Days = 0;
    short Hours = 0;
    short Minutes = 0;
    short Seconds = 0;
    short DaysInBaseMonth = (short)(DateTime.DaysInMonth(then.Year, then.Month));

    Years = (short)(now.Year - then.Year);

    Months = (short)(now.Month - then.Month);
    if (Months < 0)
    {
        Months += 12;
        Years--; // add 1 year to months, and remove 1 year from years.
    }

    Days = (short)(now.Day - then.Day);
    if (Days < 0)
    {
        Days += DaysInBaseMonth;
        Months--;
    }

    Hours = (short)(now.Hour - then.Hour);
    if (Hours < 0)
    {
        Hours += 24;
        Days--;
    }

    Minutes = (short)(now.Minute - then.Minute);
    if (Minutes < 0)
    {
        Minutes += 60;
        Hours--;
    }

    Seconds = (short)(now.Second - then.Second);
    if (Seconds < 0)
    {
        Seconds += 60;
        Minutes--;
    }

    // This is the display functionality.
    StringBuilder TimePeriod = new StringBuilder();
    short NumberOfPeriodUnitsAdded = 0;

    if (Years > 0)
    {
        TimePeriod.Append(Years);
        TimePeriod.Append(" year" + (Years != 1 ? "s" : "") + ", ");
        NumberOfPeriodUnitsAdded++;
    }

    if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
    {
        goto ParseAndReturn;
    }

    if (Months > 0)
    {
        TimePeriod.AppendFormat(Months.ToString());
        TimePeriod.Append(" month" + (Months != 1 ? "s" : "") + ", ");
        NumberOfPeriodUnitsAdded++;
    }

    if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
    {
        goto ParseAndReturn;
    }

    if (Days > 0)
    {
        TimePeriod.Append(Days);
        TimePeriod.Append(" day" + (Days != 1 ? "s" : "") + ", ");
        NumberOfPeriodUnitsAdded++;
    }

    if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
    {
        goto ParseAndReturn;
    }

    if (Hours > 0)
    {
        TimePeriod.Append(Hours);
        TimePeriod.Append(" hour" + (Hours != 1 ? "s" : "") + ", ");
        NumberOfPeriodUnitsAdded++;
    }

    if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
    {
        goto ParseAndReturn;
    }

    if (Minutes > 0)
    {
        TimePeriod.Append(Minutes);
        TimePeriod.Append(" minute" + (Minutes != 1 ? "s" : "") + ", ");
        NumberOfPeriodUnitsAdded++;
    }

    if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
    {
        goto ParseAndReturn;
    }

    if (Seconds > 0)
    {
        TimePeriod.Append(Seconds);
        TimePeriod.Append(" second" + (Seconds != 1 ? "s" : "") + "");
        NumberOfPeriodUnitsAdded++;
    }

    ParseAndReturn:
    // If the string is empty, that means the datetime is less than a second in the past.
    // An empty string being passed will cause an error, so we construct our own meaningful
    // string which will still fit into the "Posted * ago " syntax.

    if (TimePeriod.ToString() == "")
    {
        TimePeriod.Append("less than 1 second");
    }

    return TimePeriod.ToString().TrimEnd(' ', ',').ToString().ReplaceLast(",", " and");
}

Метод расширения ReplaceLast:

public static string ReplaceLast(this string source, string search, string replace)
{
    int pos = source.LastIndexOf(search);

    if (pos == -1)
    {
        return source;
    }

    return source.Remove(pos, search.Length).Insert(pos, replace);
}
...