Nushellの日付・時刻計算の基本について
しばたです。
先月ごろから普段使いのシェルをPowerShellからNushellに切り替えて実用に耐えるか検証中です。
現時点では「完全切替は難しい。」と感じつつもNushell 8 : PowerShell 2くらいの比率にはできているので存外いい感じです。
そんな中で両者ともに特徴のある日付や時刻の扱いについて比較しつつ語りたいと思います。
はじめに
比較用 : PowerShellでの日付・時刻計算
まずは比較用にPowerShellでの日付・時刻の取り扱いについて説明します。
PowerShellは.NETのオブジェクトを扱うシェルであり、日付や時刻は.NETのSystem.DateTime型(構造体)やSystem.TimeSpam型(構造体)をそのまま採用しています。
# 現在時刻を得るコマンドレット
PS C:\> Get-Date
2024年7月7日 10:55:55
# 得られる値は System.DateTime 型のオブジェクト
PS C:\> Get-Date | Get-Member
TypeName: System.DateTime
Name MemberType Definition
---- ---------- ----------
Add Method datetime Add(timespan value)
AddDays Method datetime AddDays(double value)
AddHours Method datetime AddHours(double value)
AddMilliseconds Method datetime AddMilliseconds(double value)
AddMinutes Method datetime AddMinutes(double value)
# ・・・後略・・・
# System.DateTime型 を直接扱うことも可能
PS C:\> [Datetime]::Now
2024年7月7日 10:57:49
このため日付計算のためのプロパティやメソッドを直接利用することができます。
これはPowerShellの非常に良い点であり、PowerShellでは日付・時刻の計算がめちゃくちゃ楽にできます。
# 現在時刻を変数に保存
PS C:\> $v1 = Get-Date
PS C:\> $v1
2024年7月7日 11:31:10
# .NETのメソッドで日付・時刻計算が非常に楽にできる
PS C:\> $v2 = $v1.AddDays(10) # 10日後を取得
PS C:\> $v2
2024年7月17日 11:31:10
# 時刻同士の演算も可能
PS C:\> $v2 - $v1 | Select-Object TotalHours
TotalHours
----------
240
ちょっとした応用例として「当月の月末日」を求めようとすると以下の様に書けます。
# 「翌月1日の前日」を計算することで月末日を算出
# .NETのメソッドやプロパティを使うことで簡単に計算できる
Get-Date | % { (Get-Date -Year $_.Year -Month $_.Month -Day 1).AddMonths(1).AddDays(-1).Date }
結果はこんな感じ。
# 当月(2024年7月)の月末日を取得
PS C:\> Get-Date | % { (Get-Date -Year $_.Year -Month $_.Month -Day 1).AddMonths(1).AddDays(-1).Date }
2024年7月31日 0:00:00
# うるう年も問題無し : 2024年2月の月末日
PS C:\> [datetime]::Parse('2024-02-02') | % { (Get-Date -Year $_.Year -Month $_.Month -Day 1).AddMonths(1).AddDays(-1).Date }
2024年2月29日 0:00:00
Nushellでの日付・時刻の扱い
対してNushellでは日付・時刻を独自のdate
型で扱います。
内部的にはchronoのDateTime型(chrono::DateTime)の様でタイムゾーン付きの時刻である点が特徴的です。
# 現在時刻を得るコマンド
~> let v1 = date now
~> $v1
Sun, 7 Jul 2024 11:50:17 +0900 (now)
# 得られる値は独自のdate型
~> date now | describe
date
# タイムゾーン付きのため変換が可能 : JST → UTC
~> $v1 | date to-timezone '+0000'
Sun, 7 Jul 2024 02:50:17 +0000 (3 minutes ago)
このdate
型には時刻計算のためのプロパティやメソッドはありませんが、代わりに時間長を表現するduration
が存在します。
duration
は単位付きの数字で記述し、現時点では以下の8単位存在します。
単位 | 時間 |
---|---|
ns | ナノ秒 |
us | マイクロ秒 |
ms | ミリ秒 |
sec | 秒 |
min | 分 |
hr | 時間 |
day | 日 |
wk | 週 |
残念ながら「月」と「年」は存在せず、GitHubを見ても取り扱いに相当難儀している様なので今後も増えることは無い予感がします。
date
型に対してこのduration
を使い演算が可能となっています。
# Nushellでは日付・時刻の演算に Duration が使える
~> let v2 = $v1 + 10day # 10日後を取得
~> $v2
Wed, 17 Jul 2024 11:50:17 +0900 (in a week)
# 時刻同士の演算も可能 : Durationを返す
~> $v2 - $v1
1wk 3day
# 表示形式を変えたい場合はformatコマンドを使う : これはstringを返す
~> $v2 - $v1 | format duration day
10 day
そしてdate
型に対するプロパティアクセスに相当するものとして型変換のコマンドが存在します。
date to-record
やdate to-table
コマンドでそれぞれの型に変換し要素の値を取得可能です。
~> $v1
Sun, 7 Jul 2024 11:50:17 +0900
# date to-record コマンドでrecord型に
~> $v1 | date to-record
╭────────────┬──────────╮
│ year │ 2024 │
│ month │ 7 │
│ day │ 7 │
│ hour │ 11 │
│ minute │ 50 │
│ second │ 17 │
│ nanosecond │ 50552100 │
│ timezone │ +09:00 │
╰────────────┴──────────╯
# date to-table コマンドでtable型に
~> $v1 | date to-table
╭───┬──────┬───────┬─────┬──────┬────────┬────────┬────────────┬──────────╮
│ # │ year │ month │ day │ hour │ minute │ second │ nanosecond │ timezone │
├───┼──────┼───────┼─────┼──────┼────────┼────────┼────────────┼──────────┤
│ 0 │ 2024 │ 7 │ 7 │ 11 │ 50 │ 17 │ 50552100 │ +09:00 │
╰───┴──────┴───────┴─────┴──────┴────────┴────────┴────────────┴──────────╯
# プロパティアクセスを代替できる
~> $v1 | date to-record | get year
2024
前述の応用例「当月の月末日」を求めようとするとこんな感じで書けます。
# 「翌月1日の前日」を計算することで月末日を算出しているが、durationに月がないため若干強引に計算している
date now | date to-record | if $in.month == 12 {$"($in.year + 1)-1-1"} else {$"($in.year)-($in.month + 1)-1"} | into datetime | $in - 1day
結果はこんな感じ。
# 当月(2024年7月)の月末日を取得
~> date now | date to-record | if $in.month == 12 {$"($in.year + 1)-1-1"} else {$"($in.year)-($in.month + 1)-1"} | into datetime | $in - 1day
Wed, 31 Jul 2024 00:00:00 +0900 (in 3 weeks)
# うるう年も問題無し : 2024年2月の月末日
~> '2024-02-02' | into datetime | date to-record | if $in.month == 12 {$"($in.year + 1)-1-1"} else {$"($in.year)-($in.month + 1)-1"} | into datetime | $in - 1day
Thu, 29 Feb 2024 00:00:00 +0900 (4 months ago)
PowerShellよりは頑張る必要がありますが、Nushellもduration
のおかげで比較的直観的に日付や時刻を取り扱えます。
補足 : その他もろもろ
最後に補足としてNushellの日付を扱ううえで役に立ちそうな情報を記載しておきます。
1. into datetime
文字列をdate
型に変換する際はinto datetime
コマンドを使います。
# 実行例
~> '2024-02-02' | into datetime
Fri, 2 Feb 2024 00:00:00 +0900 (5 months ago)
2. into duration
文字列をduration
型に変換する際はinto duration
コマンドを使います。
~> '10day' | into duration
1wk 3day
# 数字と単位の間にスペースがあるとエラーなので注意
~> '10 day' | into duration
Error: nu::shell::cant_convert_with_value
× Can't convert string `10` to duration.
╭─[entry #64:1:1]
1 │ '10 day' | into duration
· ─┬┬
· │╰── this string value...
· ╰── can't be converted to duration
╰────
help: supported units are ns, us/µs, ms, sec, min, hr, day, and wk
3. date list-timezone
タイムゾーンの一覧はdate list-timezone
コマンドで取得できます。
~> date list-timezone | where timezone =~ Asia/T
╭───┬───────────────╮
│ # │ timezone │
├───┼───────────────┤
│ 0 │ Asia/Taipei │
│ 1 │ Asia/Tashkent │
│ 2 │ Asia/Tbilisi │
│ 3 │ Asia/Tehran │
│ 4 │ Asia/Tel_Aviv │
│ 5 │ Asia/Thimbu │
│ 6 │ Asia/Thimphu │
│ 7 │ Asia/Tokyo │
│ 8 │ Asia/Tomsk │
╰───┴───────────────╯
4. cal
cal
コマンドでカレンダーを表示できます。
# 当月(2024年7月)のカレンダーを表示
~> cal
╭────┬────┬────┬────┬────┬────┬────╮
│ su │ mo │ tu │ we │ th │ fr │ sa │
├────┼────┼────┼────┼────┼────┼────┤
│ │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │
│ 7 │ 8 │ 9 │ 10 │ 11 │ 12 │ 13 │
│ 14 │ 15 │ 16 │ 17 │ 18 │ 19 │ 20 │
│ 21 │ 22 │ 23 │ 24 │ 25 │ 26 │ 27 │
│ 28 │ 29 │ 30 │ 31 │ │ │ │
╰────┴────┴────┴────┴────┴────┴────╯
出力をTableにする-t
オプションがあり、たとえば当月の月末日の日付だけでよければ以下の様にして取得することもできたりします。
# 当月の月末日(日付だけ)を取得 : 最終行の最大値から月末日を取得
~> cal -t | last | transpose prop value | filter {|v| $v.value != null } | get value | math max
31
最後に
簡単ですが以上となります。
Nushellの情報自体が少ないので記事にしてみました。
本記事の内容が誰かの役に立てば幸いです。