R

トップページ

Google
WWWを検索
サイト内を検索

Rの日時処理は一見すると複雑怪奇に思えるかも知れません。Date型、POSIXct型、POSIXlt型と日時を格納するクラスが何種類もありますし、実際にデータを格納するときは文字列としてテキストファイルに書き込むからです。全てテキスト型か数値型として処理したくなりますが、日付の処理は変則的なことが多いので、バグの発生源になります1。来年や来年度まであと何日か数えるのは意外に手間暇ですが、最近はlubridateパッケージを使うのが一般的のようですが、日時クラスを使うと簡単に処理できます。

1 POSIXct → POSIXlt

まずは、タイムゾーンUTCでのUNIX秒を保持するクラスPOSIXctと、日付構造体になっているリストPOSIXlt型の変換です。変換に意外性はないですが、POSIXctとPOSIXltの存在を覚えておきましょう。

now_ct <- Sys.time() # 現在時間を取得すると、POSIXct型で戻る
now_lt <- as.POSIXlt(now_ct)
now_lt
[1] "2022-11-14 07:47:29 JST"

なお、POSIXctとPOSIXltのスーパークラスがPOSIXltになります。

class(now_ct)
[1] "POSIXct" "POSIXt" 
class(now_lt)
[1] "POSIXlt" "POSIXt" 

2 POSIXct or POSIXlt → numeric

POSIXctとPOSIXltは数値型に変換することができます。

now_unix_time <- as.numeric(now_ct)
now_unix_time
[1] 1668379650

3 numeric → POSIXct, POSIXlt

数値型をPOSIXctもしくはPOSIXltに変換するのも簡単です。

now_ct <- as.POSIXct(now_unix_time, origin="1970-01-01", tz="Japan")
now_lt <- as.POSIXlt(now_unix_time, origin="1970-01-01", tz="Japan")

tzの引数はOlsonNames()が返す値になります。tzを省略するとデフォルトのタイムゾーンになります。

4 C言語のmktime風にPOSIXctに変換

桁上がり、桁下がり処理をしてくれないので、ISOdate(2022, 30, 1)などと引数に入れるとエラーになります。

now_ct <- ISOdate(1997, 4, 3, tz="Japan")
now_ct <- ISOdatetime(1997, 4, 3, 13, 34, 45, tz="Japan")

5 POSIXct or POSIXlt → Date型

タイムゾーンの情報が落ちますが、Date型に変換もできます。POSIXltを変換します。

d <- as.Date(now_lt); d

6 Date型 → POSIXct or POSIXlt

Date型から戻すとJSTがUTCに。

as.POSIXlt(d_lt)

7 POSIXct or POSIXlt → 文字列

文字列に直すときにはフォーマット関数が使えます。

format(now_lt, "%Y/%m/%d %H:%M:%S") # helpはstrftimeを参照
[1] "2022/11/14 07:47:29"

8 文字列 → POSIXct

文字列を日時型に変換するのも簡単です。引数formatを省略するとデフォルトのtryFormats(e.g. %Y/%m/%d)が試されます。

as.POSIXct("1997*4+3", format="%Y*%m+%d", tz="Japan")
[1] "1997-04-03 JST"
as.POSIXlt("1997*4+3", format="%Y*%m+%d", tz="Japan")
[1] "1997-04-03 JST"

正体はseq.Dateseq.POSIXtなので、ヘルプはそちらを参照しましょう。

methods(seq)
[1] seq.Date    seq.default seq.POSIXt 
see '?methods' for accessing help and source code

9 日時の桁上がり桁下がり処理

POSIXlt型にしておくと、30日後や100時間後のカレンダーの日付が簡単に得られます。ただし$yearが年から1900を引いた値(e.g. 2022年だと122),$monが月から1を引いた値(e.g. 12月だと11)なのには注意しましょう。

format(now_lt, "%Y/%m/%d %H:%M:%S")
[1] "2022/11/14 07:47:29"
now_lt$mday <- now_lt$mday + 30 # 30日後にセット
format(now_lt, "%Y/%m/%d %H:%M:%S")
[1] "2022/12/14 07:47:29"
now_lt$hours <- now_lt$hours + 100 # さらに100時間後にセット
format(now_lt, "%Y/%m/%d %H:%M:%S")
[1] "2022/12/14 07:47:29"

10 roundで丸め処理ができる

methods(round)をするとround.POSIXtが正体だと分かり、?round.POSIXtでヘルプに辿り着けますが、“secs”, “mins”, “hours”, “days”, “months”, “years”で丸め処理ができます。

round(as.POSIXlt("1997-4-3 12:34:56"), "mins")
[1] "1997-04-03 12:35:00 JST"

11 シーケンス生成もできる

毎週の日付、計算するのは大変ですが、簡単に出ます。

seq(as.Date("2022/2/24"), as.Date("2022/10/13"), "weeks")
 [1] "2022-02-24" "2022-03-03" "2022-03-10" "2022-03-17" "2022-03-24"
 [6] "2022-03-31" "2022-04-07" "2022-04-14" "2022-04-21" "2022-04-28"
[11] "2022-05-05" "2022-05-12" "2022-05-19" "2022-05-26" "2022-06-02"
[16] "2022-06-09" "2022-06-16" "2022-06-23" "2022-06-30" "2022-07-07"
[21] "2022-07-14" "2022-07-21" "2022-07-28" "2022-08-04" "2022-08-11"
[26] "2022-08-18" "2022-08-25" "2022-09-01" "2022-09-08" "2022-09-15"
[31] "2022-09-22" "2022-09-29" "2022-10-06" "2022-10-13"
seq(as.POSIXlt("1997-4-3 12:34:56"), as.POSIXlt("1997-4-5 12:34:56"), "12 hours")
[1] "1997-04-03 12:34:56 JST" "1997-04-04 00:34:56 JST"
[3] "1997-04-04 12:34:56 JST" "1997-04-05 00:34:56 JST"
[5] "1997-04-05 12:34:56 JST"

12 応用例① 来年まであと何日?

こんな感じで。月や日を増減する場合はPOSIXlt型を書き替えて、formatで出さないと桁上がり桁下がり処理がされません。

now_lt <- as.POSIXlt(Sys.time())
# 今年(now_lt$year + 1900) + 1は来年
newyear_ct <- ISOdate(now_lt$year + 1900 + 1, 1, 1, tz="Japan")
# 差分をとる前にDate型にして端数を抑制
difftime(as.Date(newyear_ct), as.Date(now_lt), units="days")
Time difference of 48 days

13 応用例② {年/月}の形式を月末日でPOSIXltに変更

月別の時系列データはそこそこあるのですが、当然、1994/01と言うように日は入っていません。日付型として扱うのを諦めてもよいのですが、月初や月末の日付にしておく方がプロットなどがしやすいときもあります。

ymString <- "1994/01"
ymd_begin <- as.POSIXlt(sub("([0-9]+)/([0-9]+)", "\\1/\\2/1", ymString))
ymd_end <- ymd_begin; ymd_end$mon <- ymd_end$mon + 1; ymd_end$mday <- 0
paste("月初", format(ymd_begin))
[1] "月初 1994-01-01"
paste("月末", format(ymd_end))
[1] "月末 1994-01-31"

  1. 2000年問題がありました。↩︎