LocalToUtc.psm1

function Convert-UtcToLocal
{
<#
.Synopsis
    Converts UTC time to the system local time.
.Description
    Converts the current UTC time to the local system time, using the local system's timezone.
    To override the UTC Time, enter an ISO 8601 string (ex: "2017-08-01 09:20:00") as a parameter.
    To override the Time Zone use any of the time zone strings listed in https://msdn.microsoft.com/en-us/library/cc749073.aspx
.Example
    Convert-UtcToLocal -AddHours -3 -UtcTime "2017-08-01 03:00:00"
.Example
    Convert-UtcToLocal -TimeZone "Pacific Standard Time"
#>

    [CmdletBinding()]
    
    param(
    [parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [DateTime] $Time,
    [String] $TimeZone,
    [Int] $AddDays,
    [Int] $AddHours,
    [Int] $AddMinutes
    )

    Process {
        $tzone = if ($TimeZone) { $TimeZone } else { Invoke-GetTimeZone }
        $utc = if ($Time) { $Time } else { Get-UtcTime }

        $t = Get-InputTime -Time:$utc -AddDays:$AddDays -AddHours:$AddHours -AddMinutes:$AddMinutes
        $result = Convert-TimeZone -Time:$t -ToTimeZone $tzone -FromTimeZone "UTC" -Verbose
        
        if (IsVerbose $Verbose) {
            $converted = New-Object psobject -Property @{
                UtcTime=$result.Time;
                LocalTime=$result.ToTime;
                TimeZone=$result.ToTimeZone
            }
            return $converted
        }

        return $result.ToTime
    }
}

function Convert-LocalToUtc
{
<#
.Synopsis
    Converts the local system time to UTC time
.Description
    Converts the current local system time to UTC time, using the local system's timezone.
    To override the local time, enter an ISO 8601 string (ex: "2017-08-01 09:20:00") as a parameter.
    To override the Time Zone use any of the strings listed in https://msdn.microsoft.com/en-us/library/cc749073.aspx
.Example
    Convert-LocalToUtc -AddHours 5
.Example
    Convert-LocalToUtc -TimeZone "Pacific Standard Time"
#>

    [CmdletBinding()]

    param(
    [parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [DateTime] $Time,
    [String] $TimeZone,
    [Int] $AddDays,
    [Int] $AddHours,
    [Int] $AddMinutes
    )

    Process {
        $tzone = if ($TimeZone) { $TimeZone } else { Invoke-GetTimeZone }

        $local = if ($Time) { $Time } else { Get-LocalTime (Get-UtcTime) }
        $local = Get-InputTime -Time:$local -AddDays:$AddDays -AddHours:$AddHours -AddMinutes:$AddMinutes
        
        # If a time is not defined, but a timezone is, then treat the
        # current local system time as the local time of the specified timezone
        if (! $Time -and $TimeZone) {
            $FromTimeZone = [TimeZoneInfo]::FindSystemTimeZoneById($tzone)
            $ToTimeZone = [TimeZoneInfo]::FindSystemTimeZoneById($TimeZone)
            $local = [TimeZoneInfo]::ConvertTime($local, $FromTimeZone, $ToTimeZone)
        }

        $result = Convert-TimeZone -Time $local -FromTimeZone $tzone -ToTimeZone "UTC" -Verbose
        if (IsVerbose $Verbose) {
            $converted = New-Object psobject -Property @{
                UtcTime=$result.ToTime;
                LocalTime=$result.Time;
                TimeZone=$result.FromTimeZone
            }
                
            return $converted
        }

        return $result.ToTime
    }
}

function Convert-TimeZone
{
    [CmdletBinding()]
    param(
        [parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [DateTime] $Time,
        [String] $ToTimeZone,
        [String] $FromTimeZone
    )

    Process {
        # always start with the current utc time if $Time is not set
        $fromTimeUtc = $inputTime = if ($Time) { $Time } else { Get-UtcTime }

        # if from time zone is not specified, use the current system timezone
        $fromTz = Invoke-GetTimeZone
        if ($FromTimeZone) {
            $fromTz = [TimeZoneInfo]::FindSystemTimeZoneById($FromTimeZone)
        } else {
            $fromTz = [TimeZoneInfo]::FindSystemTimeZoneById($fromTz)
        }

        if (! $ToTimeZone) { return }
        $toTz = [TimeZoneInfo]::FindSystemTimeZoneById($ToTimeZone)

        # convert the input time to utc using the FromTimeZone
        if ($Time) {
            $fromTimeKind = [System.DateTime]::SpecifyKind($Time, [System.DateTimeKind]::Unspecified)
            $fromTimeUtc = [TimeZoneInfo]::ConvertTimeToUTC($fromTimeKind, $fromTz)
        }

        # use the current utc time to find the time in the destination timezone
        $toTimeKind = [System.DateTime]::SpecifyKind($fromTimeUtc, [System.DateTimeKind]::Unspecified)
        $toTime = [TimeZoneInfo]::ConvertTimeFromUTC($toTimeKind, $toTz)

        if (IsVerbose $Verbose) {
            $result = New-Object psobject -Property @{
                Time=$inputTime;
                FromTimeZone=$fromTz;
                ToTimeZone=$toTz;
                ToTime=$toTime }
            return $result
        }

        return $toTime
    }
}

function IsVerbose ([Switch] $Verbose) {
    $IsVerbose = $PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent
    return $IsVerbose -eq $true
}

function Get-InputTime {
    Param (
        [String] $Time,
        [Int] $AddDays,
        [Int] $AddHours,
        [Int] $AddMinutes
    )
    if ($Time) { $t = Get-Date $Time }
    if ($AddDays) { $t = $t.AddDays($AddDays) }
    if ($AddHours) { $t = $t.AddHours($AddHours) }
    if ($AddMinutes) { $t = $t.AddMinutes($AddMinutes) }
    return $t
}

# overloaded to help with testing
function Get-UtcTime { return [System.DateTime]::UtcNow }
function Get-LocalTime ($time) { return $time.ToLocalTime() }
function Invoke-GetTimeZone {
    if (Get-Command Get-TimeZone -errorAction SilentlyContinue) {
        return Get-TimeZone | Select-Object -ExpandProperty Id
    }
    
    return tzutil /g
}

# Register the TimeZone parameter completers
if ($PSVersionTable.PSVersion.Major -ge 5) {
    write-host $PSScriptRoot
    . $PSScriptRoot\TimeZone-Completer.ps1
    Register-TimeZoneCompleters
}

Export-ModuleMember -function Convert-LocalToUtc
Export-ModuleMember -function Convert-UtcToLocal
Export-ModuleMember -function Convert-TimeZone