PowerShell, PowerShell, сколько мне лет?

Как и обещал, эта шуточная статья написана про то, как рассчитать ваш возраст, в продолжение предыдущей статьи о времени.

Я часто забываю сколько мне лет и не я один, и пусть скрипт шуточный, но шутка настолько забавная, что этот скрипт я выложил на powershellgallery, посмотрим, сколько еще ITшников забывают, сколько им лет 🙂

Кстати, для тех, кто не знает про powershellgallery, установить скрипт можно так:

Install-Script Get-MyAge -Force

Правда в процессе написания скрипта я понял что есть еще несколько вопросов, связанных с возрастом, на которые иногда хочется знать ответ: в какой день недели я родился, сколько дней осталось до моего следующего дня рождения и в какой день недели он будет. Ну и, конечно, хочется сохранять данные в свой профиль, чтоб каждый раз не вводить их. И, наверное, неплохо было бы узнать сколько вам было лет в какой-то определенный день, например, вы нашли фото вашего ребенка, на котором он с алкоголем что-то отмечает и вам интересно был ли он совершеннолетним 🙂

Продумаем логику

Класс [DateTime] имеет методы для добавления к дате временных промежутков, например AddHours,AddDays,AddMonths и AddYears. Почему я про них говорю, если мы можем вычесть одну дату из другой? Не все так просто, дело в том, что при вычитании дат мы получаем объект класса [TimeSpan], но с помощью этого класса мы никак не сможем узнать количество лет, что на мой взгляд очевидно, ведь для того, чтобы это вычислить, надо понимать сколько дней в году. А правда, сколько, 365,25? Заодно, для общего образования попробуем ответить и на этот вопрос.

Если взглянуть на вопрос чисто математически

Придется вспомнить правило, по которому добавляются високосные годы: год является високосным в двух случаях: либо он кратен 4, но при этом не кратен 100, либо кратен 400.
Запишем правило в виде выражения:
((год делится на 4) и (год не делится на 100)) или (год делится на 400)
По этому правилу мы рассчитаем сколько было високосных лет с начала нашей эры до 2000-го года, потом вычтем результат из общего количества лет, а затем рассчитаем количество дней:

PS> (1 .. 2000 | ? { (($_ % 4 -eq 0) -and ($_ % 100 -ne 0)) -or ($_ % 400 -eq 0) }).Count
485
PS>
PS> 2000 - 485
1515
PS>
PS> ((365*1515) + (366*485)) / 2000
365,2425
PS>
PS>

Получается, что в среднем в году 365,2425 дней, значит мы не знаем сколько точно дней в году.
Значит именно поэтому нельзя математически рассчитать количество лет и именно поэтому в классе TimeSpan отсутствует свойство с количеством лет (и, кстати, месяцев)
Но ведь каждый человек может сказать сколько ему лет.

Нужно отбросить математику и включить логику

Предположим, что вам 25 лет, вы точно знаете, что вам 25 потому, что неважно, сколько было високосных лет с момента вашего рождения, вы просто отмечаете свой день рождения в одну и ту же дату и считаете, что между двумя такими датами ровно год. Тогда по такой логике должна работать и наша программа.

Итак, открываем текстовый редактор и за дело!

Заставим наш скрипт работать по той же логике, что и человек.
Будем отталкиваться от даты рождения.
Создадим параметры WasBorn (дата рождения) и ToDay (по умолчанию — текущее время)
Организуем цикл, первым элементом которого будет год рождения, на каждой итерации к массиву с датами будет добавляться новый объект класса DateTime, который на год больше предыдущего и прервем цикл, когда вычисленная в этом объекте дата станет больше текущей даты (или просто не будем прерывать цикл, пока эта дата меньше).

Foreach ($Year in ($WasBorn.Year .. $ToDay.Year)) {
    $BirthDay = $WasBorn.Date | Get-Date -Year $Year
    if($BirthDay -le $ToDay){
        $BirthDays += @($BirthDay)
    }
}

2-я строка может показаться запутанной, но я считаю её довольно элегантной. Здесь дата передается по конвейеру в командлет Get-Date и через параметр Year указывается год. Работает это потому, что при передаче по конвейеру свойства объекта передаются как «менее приоритетные» именованные параметры в следующий элемент конвейера. Например, передадим вывод командлета Get-Date (то есть дату) по конвейеру в тот же командлет Get-Date, но во втором укажем другой год

PS> Get-Date -Year 2000 -Month 2 -Day 29 | Get-Date -Year 2001

28 февраля 2001 г. 1:15:18


PS>

Прелесть в том, что Get-Date сам догадался, что в 2001-м году не было 29-го февраля и решил что 28-е февраля — это целый год с 29-го февраля 2000-го года. Но самое важное, что 2004-й год был високосным и поэтому, если к 29 февраля 2000г. прибавить 4 года, то это будет 29 февраля 2004г.
Таким образом у нас появляется возможность избавиться от ошибок с прибавлением лет: на каждой итерации цикла не нужно увеличивать последнее значение на 1 год, нужно прибавлять на единицу большее значение к дате рождения, чем в прошлый раз.

Цикл завершился, но первый элемент массива — это дата рождения, т.е. нам было ноль лет, значит отбросим первый элемент массива и получим количество полных лет.
Все остальные операции элементарны и, если вы прочитали мою прошлую публикацию, не должны вызвать никаких затруднений.

Код скрипта:

Function Get-MyAge {
param(
[datetime]$WasBorn,
[datetime]$ToDay = (Get-Date).Date,
[switch]$SaveMyBirthDay
)

	$FilePath = "$env:APPDATA\Microsoft\Windows\PowerShell\Get-MyAge\MyBirthDay"

	if($SaveMyBirthDay -and $WasBorn){
		New-Item -Type File -Path $FilePath -Value $WasBorn.ToString('yyyy-MM-dd') -Force | Out-Null
	}
	if(!$WasBorn){
		if((Test-Path $FilePath  -ErrorAction SilentlyContinue)){
			$WasBorn = [datetime]::ParseExact($(Get-Content -Path $FilePath -ErrorAction SilentlyContinue),'yyyy-MM-dd',$NULL).Date
		} else {
			$WasBorn = (Get-Date).AddYears(-18).Date
		}

	}

	Foreach ($Year in ($WasBorn.Year .. $ToDay.Year)) {
		$BirthDay = $WasBorn.Date | Get-Date -Year $Year
		if($BirthDay -le $ToDay){
			$BirthDays += @($BirthDay)
		}
	}
	[pscustomobject][ordered]@{
	'FullYears' = [int]$($BirthDays.Count - 1)
	'Age' = [string]$("$($BirthDays.Count - 1) Y & $( ($ToDay - $BirthDays[-1]).TotalDays ) d")
	'FirstBirthDay' = $WasBorn.ToString('yyyy-MM-dd')
	'FirstBirthDayOfWeek' = $WasBorn.DayOfWeek
	'LastBirthDay' = $BirthDays[-1].ToString('yyyy-MM-dd')
	'LastBirthDayOfWeek' = $BirthDays[-1].DayOfWeek
	'NextBirthDay' = $BirthDays[-1].AddYears(1).ToString('yyyy-MM-dd')
	'NextBirthDayOfWeek' = $BirthDays[-1].AddYears(1).DayOfWeek
	'NextBirthDaysRemaining' = [int]$(($BirthDays[-1].AddYears(1) - $ToDay).TotalDays)
	'ToDay' = $ToDay.ToString('yyyy-MM-dd')
	}

}

 

Теперь представим, что Цой жив мы и хотим узнать сколько ему было бы лет

PS> Get-MyAge 1962.06.21


FullYears              : 55
Age                    : 55 Y & 36 d
FirstBirthDay          : 1962-06-21
FirstBirthDayOfWeek    : Thursday
LastBirthDay           : 2017-06-21
LastBirthDayOfWeek     : Wednesday
NextBirthDay           : 2018-06-21
NextBirthDayOfWeek     : Thursday
NextBirthDaysRemaining : 329
ToDay                  : 2017-07-27



PS>

 

А теперь добавим дату смерти, чтобы узнать сколько ему было

PS> Get-MyAge -WasBorn 1962.06.21 -ToDay 1990.08.15


FullYears              : 28
Age                    : 28 Y & 55 d
FirstBirthDay          : 1962-06-21
FirstBirthDayOfWeek    : Thursday
LastBirthDay           : 1990-06-21
LastBirthDayOfWeek     : Thursday
NextBirthDay           : 1991-06-21
NextBirthDayOfWeek     : Friday
NextBirthDaysRemaining : 310
ToDay                  : 1990-08-15



PS>

Ну и, если вы не хотите постоянно вводить свою дату рождения, используйте параметр SaveMyBirthDay и дата рождения сохраняется в текстовый файл в вашем профиле.

Поскольку программа шуточная, если дата не указана и не сохранена, то вам всегда 18 🙂

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход /  Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

Connecting to %s