From f82280839829414d0f93e80166e9480cf1a543c5 Mon Sep 17 00:00:00 2001
From: Matthew Dowst <7384306+mdowst@users.noreply.github.com>
Date: Tue, 9 Jul 2024 16:57:53 -0500
Subject: [PATCH] Add Suntime (#6)
* v1.0.2 release
---
.github/workflows/build.yml | 2 +-
README.md | 3 +-
Source/Classes/DateTimeExtended.ps1 | 4 +-
Source/Classes/SunTime.ps1 | 46 +++++
Source/Formatting/DateTimeExtended.format.ps1 | 19 ++
Source/Formatting/SunTime.format.ps1 | 29 +++
Source/PSDates.EzFormat.ps1 | 39 ++++
Source/PSDates.format.ps1xml | 188 ++++++++++++++++++
Source/PSDates.psd1 | 6 +-
Source/PSDates.psm1 | 9 +-
Source/Public/Get-DateFormat.ps1 | 24 ++-
Source/Public/Get-SunTime.ps1 | 124 ++++++++++++
Source/Public/New-Duration.ps1 | 40 ----
Source/Resources/ArgumentCompleters.ps1 | 32 +++
Source/Test/Public/Get-DateExtended.Tests.ps1 | 2 +-
Source/Test/Public/Get-SunTime.Tests.ps1 | 39 ++++
.../ScriptAnalyzer/ScriptAnalyzer.Linter.ps1 | 4 +-
build.ps1 | 23 ++-
docs/Get-DateFormat.md | 34 +++-
docs/Get-SunTime.md | 182 +++++++++++++++++
docs/New-Duration.md | 52 +++--
21 files changed, 815 insertions(+), 86 deletions(-)
create mode 100644 Source/Classes/SunTime.ps1
create mode 100644 Source/Formatting/DateTimeExtended.format.ps1
create mode 100644 Source/Formatting/SunTime.format.ps1
create mode 100644 Source/PSDates.EzFormat.ps1
create mode 100644 Source/PSDates.format.ps1xml
create mode 100644 Source/Public/Get-SunTime.ps1
create mode 100644 Source/Resources/ArgumentCompleters.ps1
create mode 100644 Source/Test/Public/Get-SunTime.Tests.ps1
create mode 100644 docs/Get-SunTime.md
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 68323fa..def1ac5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -20,7 +20,7 @@ jobs:
shell: pwsh
run: |
Set-PSRepository PSGallery -InstallationPolicy Trusted
- Install-Module ModuleBuilder,PSScriptAnalyzer
+ Install-Module ModuleBuilder,PSScriptAnalyzer,EZOut
Install-Module Pester -MinimumVersion 5.5.0
- name: Run build
diff --git a/README.md b/README.md
index dc6ba89..844f635 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,7 @@ Install-Module PSDates
| [Get-DateFormat](docs/Get-DateFormat.md) | Returns common date and time formats |
| [Get-Easter](docs/Get-Easter.md) | This function offers a generic Easter computing method for any given year, using Western, Orthodox or Julian algorithms. |
| [Get-PatchTuesday](docs/Get-PatchTuesday.md) | Returns the second Tuesday of the month |
-| [New-Duration](docs/New-Duration.md) | Short description |
+| [Get-SunTime](docs/Get-SunTime.md) | Find sunrise and sunset times for any location on planet Earth. |
+| [New-Duration](docs/New-Duration.md) | Calculates the time span between two dates and returns the duration in the ISO 8601 format |
| [Test-CrontabSchedule](docs/Test-CrontabSchedule.md) | Tests that a crontab string is valid |
diff --git a/Source/Classes/DateTimeExtended.ps1 b/Source/Classes/DateTimeExtended.ps1
index b43fe13..b9062cc 100644
--- a/Source/Classes/DateTimeExtended.ps1
+++ b/Source/Classes/DateTimeExtended.ps1
@@ -8,7 +8,7 @@ class DateTimeExtended {
[datetime]$EndOfMonth
[string]$WeekOfYear
[System.TimeZoneInfo]$TimeZone
- [int]$Quater
+ [int]$Quarter
[datetime]$Date
[int]$Day
[System.DayOfWeek]$DayOfWeek
@@ -39,7 +39,7 @@ class DateTimeExtended {
$this.EndOfMonth = ((($StartOfMonth).AddMonths(1).AddSeconds(-1)))
$this.WeekOfYear = (Get-Date $date -uformat %V)
$this.TimeZone = ([System.TimeZoneInfo]::Local)
- $this.Quater = [Math]::ceiling($Date.Month / 3)
+ $this.Quarter = [Math]::ceiling($Date.Month / 3)
$this.Date = $Date.Date
$this.Day = $Date.Day
$this.DayOfWeek = $Date.DayOfWeek
diff --git a/Source/Classes/SunTime.ps1 b/Source/Classes/SunTime.ps1
new file mode 100644
index 0000000..9b958dd
--- /dev/null
+++ b/Source/Classes/SunTime.ps1
@@ -0,0 +1,46 @@
+class SunTime {
+ [double] $Latitude
+ [double] $Longitude
+ [int64] $Now
+ [double] $JulianDate
+ [double] $JulianDay
+ [double] $MeanSolarTime
+ [double] $SolarMeanAnomaly
+ [double] $EquationOfTheCenter
+ [double] $EclipticLongitude
+ [double] $SolarTransitTime
+ [double] $HourAngle
+ [DateTime] $Sunrise
+ [DateTime] $Sunset
+ [double] $DayLength
+ [TimeZoneInfo] $TimeZone
+
+ [string] ToDegreeString([double] $value) {
+ $x = [math]::Round($value * 3600)
+ $num = "∠{0:N3}°" -f $value
+ $rad = "∠{0:N3}rad" -f ($value * ([math]::PI / 180))
+ $human = "∠{0}°{1}′{2}″" -f ($x / 3600), ($x / 60 % 60), ($x % 60)
+ return "$rad = $human = $num"
+ }
+
+ [string] FromTimestamp([double]$Timestamp,
+ [System.TimeZoneInfo]$TimeZone = $null) {
+ $datetime = ConvertFrom-UnixTime $Timestamp
+ if ($TimeZone) {
+ $datetime = [System.TimeZoneInfo]::ConvertTimeFromUtc($datetime, $TimeZone)
+ }
+ return $datetime.ToString()
+ }
+
+ [double] JulianToTimestamp(
+ [double]$Julian
+ ) {
+ return ($Julian - 2440587.5) * 86400
+ }
+
+ [double] TimestampToJulian (
+ [double]$Timestamp
+ ) {
+ return $Timestamp / 86400.0 + 2440587.5
+ }
+}
\ No newline at end of file
diff --git a/Source/Formatting/DateTimeExtended.format.ps1 b/Source/Formatting/DateTimeExtended.format.ps1
new file mode 100644
index 0000000..3dbd698
--- /dev/null
+++ b/Source/Formatting/DateTimeExtended.format.ps1
@@ -0,0 +1,19 @@
+$PropertiesAndWidths = [ordered]@{
+ Date = 25
+ WeekOfYear = 10
+ DayOfWeek = 15
+ StartOfWeek = 15
+ EndOfWeek = 15
+ StartOfMonth = 15
+ EndOfMonth = 15
+}
+$VirtualProperties = [ordered]@{
+ "StartOfWeek"= {$_.EndOfWeek.ToShortDateString()}
+ "EndOfWeek"={$_.EndOfWeek.ToShortDateString()}
+ "StartOfMonth"={$_.StartOfMonth.ToShortDateString()}
+ "EndOfMonth"={$_.EndOfMonth.ToShortDateString()}
+}
+
+$Property = $PropertiesAndWidths.GetEnumerator() | ForEach-Object { $_.Name }
+$Width = $PropertiesAndWidths.GetEnumerator() | ForEach-Object { $_.Value }
+Write-FormatView -TypeName DateTimeExtended -Property $Property -Width $Width -VirtualProperty $VirtualProperties
\ No newline at end of file
diff --git a/Source/Formatting/SunTime.format.ps1 b/Source/Formatting/SunTime.format.ps1
new file mode 100644
index 0000000..2e430af
--- /dev/null
+++ b/Source/Formatting/SunTime.format.ps1
@@ -0,0 +1,29 @@
+$VirtualProperties = [ordered]@{
+ "Date" = { $_.Sunrise.ToString('d') }
+ "Sunrise" = { $_.Sunrise.ToString('t') }
+ "Sunset" = { $_.Sunset.ToString('t') }
+ "DayLength" = { "{0:N3} hours" -f $_.DayLength }
+}
+$Property = $VirtualProperties.GetEnumerator() | ForEach-Object { $_.Name }
+Write-FormatView -TypeName SunTime -Property $Property -VirtualProperty $VirtualProperties
+
+$VirtualProperties = [ordered]@{
+ Date = { $_.Sunrise.ToString('d') }
+ Sunrise = { $_.Sunrise.ToString('t') }
+ Sunset = { $_.Sunset.ToString('t') }
+ DayLength = { "{0:N3} hours" -f $_.DayLength }
+ Latitude = { $_.ToDegreeString($_.Latitude) }
+ Longitude = { $_.ToDegreeString($_.Longitude) }
+ Now = { $_.FromTimestamp($_.Now, $_.TimeZone) }
+ JulianDate = { "{0:N3} days" -f $_.JulianDate }
+ JulianDay = { "{0:N3} days" -f $_.JulianDay }
+ MeanSolarTime = { "{0:N9} days" -f $_.MeanSolarTime }
+ SolarMeanAnomaly = { $_.ToDegreeString($_.SolarMeanAnomaly) }
+ EquationOfTheCenter = { $_.ToDegreeString($_.EquationOfTheCenter) }
+ EclipticLongitude = { $_.ToDegreeString($_.EclipticLongitude) }
+ SolarTransitTime = { $_.FromTimestamp($_.JulianToTimestamp($_.SolarTransitTime), $_.TimeZone) }
+ HourAngle = { $_.ToDegreeString($_.HourAngle) }
+}
+
+$Property = $VirtualProperties.GetEnumerator() | ForEach-Object { $_.Name }
+Write-FormatView -TypeName SunTime -Property $Property -VirtualProperty $VirtualProperties -AsList
\ No newline at end of file
diff --git a/Source/PSDates.EzFormat.ps1 b/Source/PSDates.EzFormat.ps1
new file mode 100644
index 0000000..1f6988d
--- /dev/null
+++ b/Source/PSDates.EzFormat.ps1
@@ -0,0 +1,39 @@
+#requires -Module EZOut
+# Install-Module EZOut or https://github.com/StartAutomating/EZOut
+$myFile = $MyInvocation.MyCommand.ScriptBlock.File
+$myModuleName = $($myFile | Split-Path -Leaf) -replace '\.ezformat\.ps1', '' -replace '\.ezout\.ps1', ''
+$myRoot = $myFile | Split-Path
+Push-Location $myRoot
+$formatting = @(
+ # Add your own Write-FormatView here,
+ # or put them in a Formatting or Views directory
+ foreach ($potentialDirectory in 'Formatting','Views','Types') {
+ Join-Path $myRoot $potentialDirectory |
+ Get-ChildItem -ea ignore |
+ Import-FormatView -FilePath {$_.Fullname}
+ }
+)
+
+$destinationRoot = $myRoot
+
+if ($formatting) {
+ $myFormatFilePath = Join-Path $destinationRoot "$myModuleName.format.ps1xml"
+ # You can also output to multiple paths by passing a hashtable to -OutputPath.
+ $formatting | Out-FormatData -Module $MyModuleName -OutputPath $myFormatFilePath
+}
+
+$types = @(
+ # Add your own Write-TypeView statements here
+ # or declare them in the 'Types' directory
+ Join-Path $myRoot Types |
+ Get-Item -ea ignore |
+ Import-TypeView
+
+)
+
+if ($types) {
+ $myTypesFilePath = Join-Path $destinationRoot "$myModuleName.types.ps1xml"
+ # You can also output to multiple paths by passing a hashtable to -OutputPath.
+ $types | Out-TypeData -OutputPath $myTypesFilePath
+}
+Pop-Location
diff --git a/Source/PSDates.format.ps1xml b/Source/PSDates.format.ps1xml
new file mode 100644
index 0000000..65e3b51
--- /dev/null
+++ b/Source/PSDates.format.ps1xml
@@ -0,0 +1,188 @@
+
+
+
+
+ DateTimeExtended
+
+ DateTimeExtended
+
+
+
+
+ left
+ 25
+
+
+ left
+ 10
+
+
+ left
+ 15
+
+
+
+ left
+ 15
+
+
+
+ left
+ 15
+
+
+
+ left
+ 15
+
+
+
+ left
+ 15
+
+
+
+
+
+
+ Date
+
+
+ WeekOfYear
+
+
+ DayOfWeek
+
+
+ $_.EndOfWeek.ToShortDateString()
+
+
+ $_.EndOfWeek.ToShortDateString()
+
+
+ $_.StartOfMonth.ToShortDateString()
+
+
+ $_.EndOfMonth.ToShortDateString()
+
+
+
+
+
+
+
+ SunTime
+
+ SunTime
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $_.Sunrise.ToString('d')
+
+
+ $_.Sunrise.ToString('t')
+
+
+ $_.Sunset.ToString('t')
+
+
+ "{0:N3} hours" -f $_.DayLength
+
+
+
+
+
+
+
+ SunTime
+
+ SunTime
+
+
+
+
+
+
+
+ $_.Sunrise.ToString('d')
+
+
+
+ $_.Sunrise.ToString('t')
+
+
+
+ $_.Sunset.ToString('t')
+
+
+
+ "{0:N3} hours" -f $_.DayLength
+
+
+
+ $_.ToDegreeString($_.Latitude)
+
+
+
+ $_.ToDegreeString($_.Longitude)
+
+
+
+ $_.FromTimestamp($_.Now, $_.TimeZone)
+
+
+
+ "{0:N3} days" -f $_.JulianDate
+
+
+
+ "{0:N3} days" -f $_.JulianDay
+
+
+
+ "{0:N9} days" -f $_.MeanSolarTime
+
+
+
+ $_.ToDegreeString($_.SolarMeanAnomaly)
+
+
+
+ $_.ToDegreeString($_.EquationOfTheCenter)
+
+
+
+ $_.ToDegreeString($_.EclipticLongitude)
+
+
+
+ $_.FromTimestamp($_.JulianToTimestamp($_.SolarTransitTime), $_.TimeZone)
+
+
+
+ $_.ToDegreeString($_.HourAngle)
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/PSDates.psd1 b/Source/PSDates.psd1
index f7a252e..b651a41 100644
--- a/Source/PSDates.psd1
+++ b/Source/PSDates.psd1
@@ -12,7 +12,7 @@
RootModule = '.\PSDates.psm1'
# Version number of this module.
-ModuleVersion = '1.0.1'
+ModuleVersion = '1.0.2'
# Supported PSEditions
# CompatiblePSEditions = @()
@@ -63,13 +63,13 @@ RequiredAssemblies = @('.\Resources\NCrontab.dll','.\Resources\CronExpressionDes
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
-FormatsToProcess = '.\Resources\DateTimeExtensions.format.ps1xml'
+FormatsToProcess = '.\PSDates.format.ps1xml'
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
-FunctionsToExport = @('Convert-TimeZone','ConvertFrom-UnixTime','ConvertFrom-WmiDateTime','ConvertTo-UnixTime','ConvertTo-WmiDateTime','Find-TimeZone','Get-CronNextOccurrence','Get-DateExtended','Get-DateFormat','Get-Easter','Get-PatchTuesday','New-Duration','Test-CrontabSchedule')
+FunctionsToExport = '*'
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @()
diff --git a/Source/PSDates.psm1 b/Source/PSDates.psm1
index 2c93658..f1e61ee 100644
--- a/Source/PSDates.psm1
+++ b/Source/PSDates.psm1
@@ -1,7 +1,8 @@
-$Public = @( Get-ChildItem -Path (Join-Path $PSScriptRoot 'Public\*.ps1') -Recurse -ErrorAction SilentlyContinue )
+$ScriptsToImport = @( Get-ChildItem -Path (Join-Path $PSScriptRoot 'Public\*.ps1') -Recurse -ErrorAction SilentlyContinue ) +
+ @( Get-ChildItem -Path (Join-Path $PSScriptRoot 'Private\*.ps1') -Recurse -ErrorAction SilentlyContinue )
#Dot source the files
-Foreach ($import in $Public) {
+Foreach ($import in $ScriptsToImport) {
Try {
Write-Verbose "dot-sourcing file '$($import.fullname)'"
. $import.fullname
@@ -9,6 +10,4 @@ Foreach ($import in $Public) {
Catch {
Write-Error -Message "Failed to import function $($import.fullname): $_"
}
-}
-
-#Add-Type -Path (Join-Path $PSScriptRoot 'Resources\ncrontab.3.3.0\lib\net35\NCrontab.dll')
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/Source/Public/Get-DateFormat.ps1 b/Source/Public/Get-DateFormat.ps1
index 4dd10fe..f3465db 100644
--- a/Source/Public/Get-DateFormat.ps1
+++ b/Source/Public/Get-DateFormat.ps1
@@ -23,17 +23,22 @@ Function Get-DateFormat {
A PSObject containing the diffent values for the datetime formats.
#>
[alias("Get-DateFormats")]
- [CmdletBinding()]
- [OutputType([DateTimeFormats])]
+ [CmdletBinding(DefaultParameterSetName = "Full")]
+ [OutputType([DateTimeFormats], ParameterSetName = "ID")]
+ [OutputType([object], ParameterSetName = "Format")]
param(
- [Parameter(Mandatory = $false)]
- [datetime]$Date = $(Get-Date)
+ [Parameter(Mandatory = $false, ParameterSetName = "Full")]
+ [Parameter(Mandatory = $false, ParameterSetName = "Format")]
+ [datetime]$Date = $(Get-Date),
+
+ [Parameter(Mandatory = $false, ParameterSetName = "Format")]
+ [string]$Format
)
$offset = ([System.TimeZoneInfo]::Local).BaseUtcOffset.ToString()
$offset = $offset.Substring(0, $offset.LastIndexOf(':'))
- [DateTimeFormats]@{
+ $dateFormats = [DateTimeFormats]@{
DateTime = $Date.DateTime
RFC1123UTC = $Date.ToUniversalTime().ToString('r')
SQL = $Date.ToString("yyyy-MM-dd HH:mm:ss.fff")
@@ -41,7 +46,7 @@ Function Get-DateFormat {
ISO8601 = $Date.ToString("yyyy-MM-ddTHH:mm:ss.fff") + $offset
ShortDate = $Date.ToString('d')
LongDate = $Date.ToString('D')
- LongDateNoDay = $Date.ToString('D').Substring($Date.ToString('D').IndexOf(',')+2)
+ LongDateNoDay = $Date.ToString('D').Substring($Date.ToString('D').IndexOf(',') + 2)
FullDateShortTime = $Date.ToString('f')
FullDateTime = $Date.ToString('F')
GeneralDateShortTime = $Date.ToString('g')
@@ -72,4 +77,11 @@ Function Get-DateFormat {
IsLeapYear = [datetime]::IsLeapYear($Date.Year)
FileTime = $Date.ToFileTime()
}
+
+ if ([string]::IsNullOrEmpty($PSBoundParameters['Format'])) {
+ $dateFormats
+ }
+ else {
+ $dateFormats."$($PSBoundParameters['Format'])"
+ }
}
diff --git a/Source/Public/Get-SunTime.ps1 b/Source/Public/Get-SunTime.ps1
new file mode 100644
index 0000000..9f2cb16
--- /dev/null
+++ b/Source/Public/Get-SunTime.ps1
@@ -0,0 +1,124 @@
+function Get-SunTime {
+ <#
+.SYNOPSIS
+Find sunrise and sunset times for any location on planet Earth.
+
+.DESCRIPTION
+This function finds the time of day for sunrise, sunset based on the given latitude and longitude. You can also specify time zone and elevation.
+
+.PARAMETER Date
+The day to find the sunrise and sunset for.
+
+.PARAMETER Latitude
+The Latitude entered as a decimal number representing degrees and minutes
+
+.PARAMETER Longitude
+The Longitude entered as a decimal number representing degrees and minutes
+
+.PARAMETER Elevation
+The Elevation in meters
+
+.PARAMETER TimeZone
+The time zone for the final results
+
+.EXAMPLE
+Get-SunTime -Latitude 51.501005 -Longitude -0.1445479
+
+# Get the sunrise and sunset for the given coordinates for the current day
+
+.EXAMPLE
+$address = '1600 Pennsylvania Avenue NW'
+$addr = Invoke-RestMethod "https://nominatim.openstreetmap.org/search?q=$($address)&format=json" | Select-Object -First 1
+Get-SunTime -Latitude $addr.lat -Longitude $addr.lon
+
+# Use the free Nominatim API get the coordinates for an address, then use those results to get the sunrise and sunset for that location.
+
+.NOTES
+Use can use Google Maps to find the latitude and longitude coordinates.
+Right click a specific point on the Google map and you will see the latitude and longitude coordinates displayed, for example 45.51421, -122.68462.
+
+#>
+ [CmdletBinding()]
+ param (
+ [datetime]$Date = $(Get-Date),
+ [double]$Latitude,
+ [double]$Longitude,
+ [double]$Elevation = 0.0,
+ [TimeZoneInfo]$TimeZone = $null
+ )
+ $suntime = [SunTime]::new()
+ $datetimeOffset = [DateTimeOffset]::new($Date)
+ $CurrentTimestamp = $datetimeOffset.ToUniversalTime().ToUnixTimeSeconds()
+
+ Write-Verbose "Latitude f = $($suntime.ToDegreeString($Latitude))"
+ Write-Verbose "Longitude l_w = $($suntime.ToDegreeString($Longitude))"
+ Write-Verbose "Now ts = $($suntime.FromTimestamp($CurrentTimestamp, $TimeZone))"
+
+
+ $J_date = $suntime.TimestampToJulian($CurrentTimestamp)
+ Write-Verbose ("Julian date j_date = {0:N3} days" -f $J_date)
+
+ # Julian day
+ $n = [math]::Ceiling($J_date - (2451545.0 + 0.0009) + 69.184 / 86400.0)
+ Write-Verbose ("Julian day n = {0:N3} days" -f $n)
+
+ # Mean solar time
+ $J_ = $n + 0.0009 - $Longitude / 360.0
+ Write-Verbose ("Mean solar time J_ = {0:N9} days" -f $J_)
+
+ # Solar mean anomaly
+ $M_degrees = [math]::IEEERemainder(357.5291 + 0.98560028 * $J_, 360)
+ $M_radians = ($M_degrees * ([math]::PI / 180))
+ Write-Verbose "Solar mean anomaly M = $($suntime.ToDegreeString($M_degrees))"
+
+ # Equation of the center
+ $C_degrees = 1.9148 * [math]::Sin($M_radians) + 0.02 * [math]::Sin(2 * $M_radians) + 0.0003 * [math]::Sin(3 * $M_radians)
+ Write-Verbose "Equation of the center C = $($suntime.ToDegreeString($C_degrees))"
+
+ # Ecliptic longitude
+ $L_degrees = [math]::IEEERemainder($M_degrees + $C_degrees + 180.0 + 102.9372, 360)
+ Write-Verbose "Ecliptic longitude L = $($suntime.ToDegreeString($L_degrees))"
+
+ $Lambda_radians = ($L_degrees * ([math]::PI / 180))
+
+ # Solar transit (julian date)
+ $J_transit = 2451545.0 + $J_ + 0.0053 * [math]::Sin($M_radians) - 0.0069 * [math]::Sin(2 * $Lambda_radians)
+ Write-Verbose "Solar transit time J_trans = $($suntime.FromTimestamp( $suntime.JulianToTimestamp($J_transit), $TimeZone))"
+
+ # Declination of the Sun
+ $sin_d = [math]::Sin($Lambda_radians) * [math]::Sin((23.4397 * ([math]::PI / 180)))
+ $cos_d = [math]::Cos([math]::Asin($sin_d))
+
+ # Hour angle
+ $some_cos = ([math]::Sin(-0.833 * [math]::PI / 180 - 2.076 * [math]::Sqrt($Elevation) / 60.0 * [math]::PI / 180) - [math]::Sin($Latitude * [math]::PI / 180) * $sin_d) / ([math]::Cos($Latitude * [math]::PI / 180) * $cos_d)
+ $w0_radians = [math]::Acos($some_cos)
+
+
+ $w0_degrees = $w0_radians * 180 / [math]::PI
+ Write-Verbose "Hour angle w0 = $($suntime.ToDegreeString($w0_degrees))"
+
+ $j_rise = $J_transit - $w0_degrees / 360
+ $j_set = $J_transit + $w0_degrees / 360
+
+ Write-Verbose "Sunrise j_rise = $($suntime.FromTimestamp( $suntime.JulianToTimestamp($j_rise), $TimeZone))"
+ Write-Verbose "Sunset j_set = $($suntime.JulianToTimestamp($j_rise)) = $($suntime.FromTimestamp($suntime.JulianToTimestamp($j_set), $TimeZone))"
+ Write-Verbose ("Day length {0:N3} hours" -f ($w0_degrees / (180 / 24)))
+
+ [SunTime]@{
+ Latitude = $Latitude
+ Longitude = $Longitude
+ Now = $CurrentTimestamp
+ JulianDate = $J_date
+ JulianDay = $n
+ MeanSolarTime = $J_
+ SolarMeanAnomaly = $M_degrees
+ EquationOfTheCenter = $C_degrees
+ EclipticLongitude = $L_degrees
+ SolarTransitTime = $J_transit
+ HourAngle = $w0_degrees
+ Sunrise = (Get-Date $($suntime.FromTimestamp($suntime.JulianToTimestamp($j_rise), $TimeZone)))
+ Sunset = (Get-Date $($suntime.FromTimestamp($suntime.JulianToTimestamp($j_set), $TimeZone)))
+ DayLength = ($w0_degrees / (180 / 24))
+ TimeZone = $TimeZone
+ }
+}
\ No newline at end of file
diff --git a/Source/Public/New-Duration.ps1 b/Source/Public/New-Duration.ps1
index 2f6c02e..aa216eb 100644
--- a/Source/Public/New-Duration.ps1
+++ b/Source/Public/New-Duration.ps1
@@ -1,43 +1,3 @@
-<#
-.SYNOPSIS
-Short description
-
-.DESCRIPTION
-Long description
-
-.PARAMETER Start
-Parameter description
-
-.PARAMETER End
-Parameter description
-
-.PARAMETER Years
-Parameter description
-
-.PARAMETER Months
-Parameter description
-
-.PARAMETER Days
-Parameter description
-
-.PARAMETER Hours
-Parameter description
-
-.PARAMETER Minutes
-Parameter description
-
-.PARAMETER Seconds
-Parameter description
-
-.PARAMETER Weeks
-Parameter description
-
-.EXAMPLE
-An example
-
-.NOTES
-General notes
-#>
Function New-Duration {
<#
.SYNOPSIS
diff --git a/Source/Resources/ArgumentCompleters.ps1 b/Source/Resources/ArgumentCompleters.ps1
new file mode 100644
index 0000000..34b73f0
--- /dev/null
+++ b/Source/Resources/ArgumentCompleters.ps1
@@ -0,0 +1,32 @@
+function GetDateFormatsCompletionResult {
+ param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
+ [DateTimeFormats]::new().psobject.Properties.Name | foreach-object {
+ New-Object -TypeName System.Management.Automation.CompletionResult -ArgumentList "'$_'",
+ $_ , ([System.Management.Automation.CompletionResultType]::ParameterValue) , $_
+ }
+}
+
+function GetTimeZoneCompletionResult {
+ param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
+ foreach ($dt in [System.TimeZoneInfo]::GetSystemTimeZones()) {
+ if ($dt.id, $dt.displayname -match $wordToComplete) {
+ [Management.Automation.CompletionResult]::new(
+ ("'{0}'" -f $dt.id),
+ $dt.DisplayName,
+ "ParameterValue",
+ ("{0}/{1} (DST:{2})" -f @(
+ $dt.StandardName
+ $dt.DaylightName
+ $dt.SupportsDaylightSavingTime
+ ))
+ )
+ }
+ }
+}
+
+if(Get-Command -ErrorAction SilentlyContinue -name Register-ArgumentCompleter) {
+ Register-ArgumentCompleter -CommandName Get-DateFormat -ParameterName Format -ScriptBlock $Function:GetDateFormatsCompletionResult
+ Register-ArgumentCompleter -CommandName Convert-TimeZone -ParameterName ToTimeZone -ScriptBlock $Function:GetTimeZoneCompletionResult
+ Register-ArgumentCompleter -CommandName Convert-TimeZone -ParameterName FromTimeZone -ScriptBlock $Function:GetTimeZoneCompletionResult
+ Register-ArgumentCompleter -CommandName Get-SunTime -ParameterName TimeZone -ScriptBlock $Function:GetTimeZoneCompletionResult
+}
\ No newline at end of file
diff --git a/Source/Test/Public/Get-DateExtended.Tests.ps1 b/Source/Test/Public/Get-DateExtended.Tests.ps1
index b212936..f970d8b 100644
--- a/Source/Test/Public/Get-DateExtended.Tests.ps1
+++ b/Source/Test/Public/Get-DateExtended.Tests.ps1
@@ -17,7 +17,7 @@ Describe 'Get-DateExtended Tests' {
$dateExt.EndOfMonth | Should -Be (Get-Date '11/30/2017 23:59:59')
$dateExt.WeekOfYear | Should -Be 46
$dateExt.TimeZone | Should -Be ([System.TimeZoneInfo]::Local)
- $dateExt.Quater | Should -Be 4
+ $dateExt.Quarter | Should -Be 4
$dateExt.Date | Should -Be (Get-Date '11/17/2017 00:00:00')
$dateExt.Day | Should -Be 17
$dateExt.DayOfWeek | Should -Be 'Friday'
diff --git a/Source/Test/Public/Get-SunTime.Tests.ps1 b/Source/Test/Public/Get-SunTime.Tests.ps1
new file mode 100644
index 0000000..ef1c21f
--- /dev/null
+++ b/Source/Test/Public/Get-SunTime.Tests.ps1
@@ -0,0 +1,39 @@
+BeforeAll {
+ # Import the module
+ $TopLevel = (Split-Path(Split-Path(Split-Path $PSScriptRoot)))
+ $ModulePath = Get-ChildItem -Path (Join-Path $TopLevel 'Build\PSDates') -Filter 'PSDates.psd1' -Recurse | Select-Object -Last 1
+ Import-Module $ModulePath.FullName -Force
+}
+
+Describe 'Get-SunTime Tests' {
+ It ' Test' -ForEach @(
+ @{Name = 'Buckingham Palace'; Latitude = 51.501005; Longitude = -0.1445479; Elevation = 0; Sunrise = '03:52'; Sunset = '20:20' }
+ @{Name = 'The Great Pyramid of Giza'; Latitude = 29.9791705; Longitude = 31.1316297; Elevation = 0; Sunrise = '03:00'; Sunset = '17:01' }
+ @{Name = 'Imperial Palace'; Latitude = 35.6778452; Longitude = 139.7547335; Elevation = 0; Sunrise = '19:31'; Sunset = '10:01' }
+ @{Name = 'The White House'; Latitude = 38.8976763; Longitude = -77.0365298; Elevation = 0; Sunrise = '09:49'; Sunset = '00:37' }
+ @{Name = 'The Eiffel Tower'; Latitude = 48.8583701; Longitude = 2.2919064; Elevation = 0; Sunrise = '03:55'; Sunset = '19:57' }
+ @{Name = 'Sydney Opera House'; Latitude = -33.8531432; Longitude = 151.1764362; Elevation = 0; Sunrise = '21:01'; Sunset = '07:00' }
+ @{Name = 'Pier 39'; Latitude = 37.7917995; Longitude = -122.4352178; Elevation = 0; Sunrise = '12:54'; Sunset = '03:36' }
+ @{Name = 'The Zocalo'; Latitude = 19.4326018; Longitude = -99.1357798; Elevation = 0; Sunrise = '12:04'; Sunset = '01:20' }
+ @{Name = 'Pike Place Market'; Latitude = 47.6096619; Longitude = -122.344816; Elevation = 0; Sunrise = '12:19'; Sunset = '04:10' }
+ @{Name = 'Tsim Sha Tsui Waterfront'; Latitude = 22.2974716; Longitude = 114.1772887; Elevation = 0; Sunrise = '21:45'; Sunset = '11:12' }
+ @{Name = 'Grand Bazaar'; Latitude = 41.0179762; Longitude = 28.9450643; Elevation = 0; Sunrise = '02:39'; Sunset = '17:40' }
+ @{Name = 'The Forbidden City'; Latitude = 39.9168038; Longitude = 116.3945872; Elevation = 0; Sunrise = '20:52'; Sunset = '11:47' }
+ @{Name = 'Niagara Falls'; Latitude = 43.0535092; Longitude = -79.3998494; Elevation = 0; Sunrise = '09:45'; Sunset = '01:00' }
+ @{Name = 'The Colosseum'; Latitude = 41.8902101; Longitude = 12.48736; Elevation = 0; Sunrise = '03:42'; Sunset = '18:49' }
+ @{Name = 'Times Square'; Latitude = 40.7579747; Longitude = -73.9881175; Elevation = 0; Sunrise = '09:31'; Sunset = '00:31' }
+ ) {
+ $TimeZone = [System.TimeZoneInfo]::Utc
+ $Date = Get-Date '2024-07-05'
+ $CalculateSunTimesParam = @{
+ Date = $Date
+ Latitude = $Latitude
+ Longitude = $Longitude
+ Elevation = $Elevation
+ TimeZone = $TimeZone
+ }
+ $Result = Get-SunTime @CalculateSunTimesParam
+ $Result.Sunrise.ToString('HH:mm') | Should -Be $Sunrise
+ $Result.Sunset.ToString('HH:mm') | Should -Be $Sunset
+ }
+}
\ No newline at end of file
diff --git a/Source/Test/ScriptAnalyzer/ScriptAnalyzer.Linter.ps1 b/Source/Test/ScriptAnalyzer/ScriptAnalyzer.Linter.ps1
index 2f8798c..227d87e 100644
--- a/Source/Test/ScriptAnalyzer/ScriptAnalyzer.Linter.ps1
+++ b/Source/Test/ScriptAnalyzer/ScriptAnalyzer.Linter.ps1
@@ -1,4 +1,4 @@
+Import-Module PSScriptAnalyzer
$Public = Join-Path (Split-Path(Split-Path $PSScriptRoot)) 'Public'
$Settings = Join-Path $PSScriptRoot 'PSScriptAnalyzerSettings.psd1'
-Invoke-ScriptAnalyzer -Path $Public -Recurse -Settings $Settings
-
+Invoke-ScriptAnalyzer -Path $Public -Recurse -Settings $Settings
\ No newline at end of file
diff --git a/build.ps1 b/build.ps1
index 42dfd1f..7098343 100644
--- a/build.ps1
+++ b/build.ps1
@@ -10,22 +10,31 @@ if(Test-Path .\Build){
Get-ChildItem -Path .\Build | Remove-Item -Recurse -Force
}
-#$linter = . '.\Source\Test\ScriptAnalyzer\ScriptAnalyzer.Linter.ps1'
-#if ($linter) {
-## $linter
-# throw "Failed linter tests"
-#}
+# Generate EzOut formaters
+. '.\Source\PSDates.EzFormat.ps1'
+
+$linter = . '.\Source\Test\ScriptAnalyzer\ScriptAnalyzer.Linter.ps1'
+if ($linter) {
+ $linter
+ throw "Failed linter tests"
+}
Build-Module -SourcePath .\Source -OutputDirectory ..\Build -Version $VersionNumber
$psd1 = Get-ChildItem .\Build -Filter 'PSDates.psd1' -Recurse | Select-Object -Last 1
+$psm1 = Get-ChildItem .\Build -Filter 'PSDates.psm1' -Recurse | Select-Object -Last 1
$ResourceFolder = Join-Path $psd1.DirectoryName 'Resources'
New-Item -Path $ResourceFolder -ItemType Directory | Out-Null
Copy-Item -Path '.\Source\Resources\CronExpressionDescriptor.dll' -Destination $ResourceFolder
Copy-Item -Path '.\Source\Resources\ncrontab.3.3.0\lib\net35\NCrontab.dll' -Destination $ResourceFolder
-Copy-Item -Path '.\Source\Resources\DateTimeExtensions.format.ps1xml' -Destination $ResourceFolder
+Copy-Item -Path '.\Source\Resources\ArgumentCompleters.ps1' -Destination $ResourceFolder
-#$nuspec = Copy-Item -Path .\Source\PSDates.nuspec -Destination $psd1.DirectoryName -PassThru
+@'
+# Argument Completers
+$ArgumentCompleters = Join-Path $PSScriptRoot 'Resources\ArgumentCompleters.ps1'
+. $ArgumentCompleters
+'@ | Out-File -LiteralPath $psm1.FullName -Append
+#$nuspec = Copy-Item -Path .\Source\PSDates.nuspec -Destination $psd1.DirectoryName -PassThru
#.'nuget.exe' pack "$($nuspec.FullName)" -OutputDirectory .\Build -Version "$($VersionNumber)"
\ No newline at end of file
diff --git a/docs/Get-DateFormat.md b/docs/Get-DateFormat.md
index 47531d2..ab6491f 100644
--- a/docs/Get-DateFormat.md
+++ b/docs/Get-DateFormat.md
@@ -13,10 +13,16 @@ Returns common date and time formats
## SYNTAX
-### __AllParameterSets
+### Full (Default)
```
-Get-DateFormat [[-Date ]] [-ProgressAction ] []
+Get-DateFormat [-Date ] [-ProgressAction ] []
+```
+
+### Format
+
+```
+Get-DateFormat [-Date ] [-Format ] [-ProgressAction ] []
```
## DESCRIPTION
@@ -61,18 +67,36 @@ The datetime value to return the formats for
```yaml
Type: DateTime
-Parameter Sets: (All)
+Parameter Sets: Format, Full
Aliases:
Accepted values:
-Required: True (None) False (All)
-Position: 0
+Required: True (None) False (Format, Full)
+Position: Named
Default value: $(Get-Date)
Accept pipeline input: False
Accept wildcard characters: False
DontShow: False
```
+### -Format
+
+{{ Fill Format Description }}
+
+```yaml
+Type: String
+Parameter Sets: Format
+Aliases:
+Accepted values:
+
+Required: True (None) False (Format)
+Position: Named
+Default value:
+Accept pipeline input: False
+Accept wildcard characters: False
+DontShow: False
+```
+
### -ProgressAction
{{ Fill ProgressAction Description }}
diff --git a/docs/Get-SunTime.md b/docs/Get-SunTime.md
new file mode 100644
index 0000000..8e10ebb
--- /dev/null
+++ b/docs/Get-SunTime.md
@@ -0,0 +1,182 @@
+---
+external help file: PSDates-help.xml
+Module Name: PSDates
+online version:
+schema: 2.0.0
+---
+
+# Get-SunTime
+
+## SYNOPSIS
+
+Find sunrise and sunset times for any location on planet Earth.
+
+## SYNTAX
+
+### __AllParameterSets
+
+```
+Get-SunTime [[-Date ]] [[-Latitude ]] [[-Longitude ]] [[-Elevation ]] [[-TimeZone ]] [-ProgressAction ] []
+```
+
+## DESCRIPTION
+
+This function finds the time of day for sunrise, sunset based on the given latitude and longitude.
+You can also specify time zone and elevation.
+
+
+## EXAMPLES
+
+### Example 1: EXAMPLE 1
+
+```
+Get-SunTime -Latitude 51.501005 -Longitude -0.1445479
+```
+
+# Get the sunrise and sunset for the given coordinates for the current day
+
+
+
+
+
+### Example 2: EXAMPLE 2
+
+```
+$address = '1600 Pennsylvania Avenue NW'
+$addr = Invoke-RestMethod "https://nominatim.openstreetmap.org/search?q=$($address)&format=json" | Select-Object -First 1
+Get-SunTime -Latitude $addr.lat -Longitude $addr.lon
+```
+
+# Use the free Nominatim API get the coordinates for an address, then use those results to get the sunrise and sunset for that location.
+
+
+
+
+
+
+## PARAMETERS
+
+### -Date
+
+The day to find the sunrise and sunset for.
+
+```yaml
+Type: DateTime
+Parameter Sets: (All)
+Aliases:
+Accepted values:
+
+Required: True (None) False (All)
+Position: 0
+Default value: $(Get-Date)
+Accept pipeline input: False
+Accept wildcard characters: False
+DontShow: False
+```
+
+### -Elevation
+
+The Elevation in meters
+
+```yaml
+Type: Double
+Parameter Sets: (All)
+Aliases:
+Accepted values:
+
+Required: True (None) False (All)
+Position: 3
+Default value: 0
+Accept pipeline input: False
+Accept wildcard characters: False
+DontShow: False
+```
+
+### -Latitude
+
+The Latitude entered as a decimal number representing degrees and minutes
+
+```yaml
+Type: Double
+Parameter Sets: (All)
+Aliases:
+Accepted values:
+
+Required: True (None) False (All)
+Position: 1
+Default value: 0
+Accept pipeline input: False
+Accept wildcard characters: False
+DontShow: False
+```
+
+### -Longitude
+
+The Longitude entered as a decimal number representing degrees and minutes
+
+```yaml
+Type: Double
+Parameter Sets: (All)
+Aliases:
+Accepted values:
+
+Required: True (None) False (All)
+Position: 2
+Default value: 0
+Accept pipeline input: False
+Accept wildcard characters: False
+DontShow: False
+```
+
+### -ProgressAction
+
+{{ Fill ProgressAction Description }}
+
+```yaml
+Type: ActionPreference
+Parameter Sets: (All)
+Aliases: proga
+Accepted values:
+
+Required: True (None) False (All)
+Position: Named
+Default value:
+Accept pipeline input: False
+Accept wildcard characters: False
+DontShow: False
+```
+
+### -TimeZone
+
+The time zone for the final results
+
+```yaml
+Type: TimeZoneInfo
+Parameter Sets: (All)
+Aliases:
+Accepted values:
+
+Required: True (None) False (All)
+Position: 4
+Default value:
+Accept pipeline input: False
+Accept wildcard characters: False
+DontShow: False
+```
+
+
+### CommonParameters
+
+This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
+
+## NOTES
+
+Use can use Google Maps to find the latitude and longitude coordinates.
+
+Right click a specific point on the Google map and you will see the latitude and longitude coordinates displayed, for example 45.51421, -122.68462.
+
+
+## RELATED LINKS
+
+Fill Related Links Here
+
diff --git a/docs/New-Duration.md b/docs/New-Duration.md
index 1a9238b..cf50eef 100644
--- a/docs/New-Duration.md
+++ b/docs/New-Duration.md
@@ -9,7 +9,7 @@ schema: 2.0.0
## SYNOPSIS
-Short description
+Calculates the time span between two dates and returns the duration in the ISO 8601 format
## SYNTAX
@@ -33,7 +33,9 @@ New-Duration [-ProgressAction ] [-Weeks ] [