link もっと前
   2009年 11月 16日 -
      2009年 11月 16日  
link もっと後

link 未来から過去へ表示(*)
link 過去から未来へ表示

日々

link permalink

経過日を年月日に変換

ある時点からの経過日(0 基点)を年月日にする、なんていかにも学校の授業で出てきそうなアルゴリズムですが、世間では需要がないのか、名前が付いているようなアルゴリズムがありません。

練習問題にちょうど良かったので、いっちょ考えてみました。鈴木アルゴリズム完成!なーんて言えれば良かったんですけど、大した物はできませんでした。

その代わりといってはなんですが、考え方を細かくメモっていきたいと思います。今日は月日の変換、後日に年の変換を扱います。

文章をうだうだ読むのが面倒くせえ!って人のために、実装例も載せる予定です。ライセンスを特に明示していなければ、修正 BSD ライセンスを適用します。

グレゴリオ歴の性質

年月日のルールを決めるのは暦です。現在採用されている暦の主流は 1582年頃に採用が始まった「グレゴリオ歴」です。下記の性質があります。

  • 1年は 365日(平年)か 366日(閏年)
  • 1年は 12ヶ月
  • 1月は 28日(平年 2月)29日(閏年 2月)30日(4,6,9,11月)31日(1,3,5,7,8,10,12月)のどれか
  • 1日は 24時間
  • 1時間は 60分
  • 1分は 60秒

閏年は暦と地球の季節がずれないようにする仕組み(※1)です。もっと簡単に言うと、平年より 1日長い年のことです。平年/閏年は下記のルールで決まります。

  1. 西暦が 4で割り切れる年は閏年(1年が 366日)
  2. 西暦が 100で割り切れる年は平年(1年が 365日)
  3. 西暦が 400で割り切れる年は閏年(1年が 366日)

変なルールに見えるかもしれませんが、人類が苦労して「時間とはなんぞや?暦とはなんぞや?」を定義してきた証なのでしょう。

(※1)地球の公転周期(1年)は自転周期(1日)の 365倍と 1/4 日くらいです。単純に暦を 1年 = 365日としてしまうと 4年で 1日分、季節と暦がずれてしまいます。その差を補正するために閏年が作られました。

邪魔な 2月

初めに経過日→月日の変換を考えます。その際に邪魔なのが年によって長さが変わる 2月です。

例として「1/1 から 60日後は何月何日か?」を考えてみます。平年(2月が 28日間)ならば答えは 3/2、閏年(2月が 29日間)ならば答えは 3/1 です。2月の存在によって、経過日と月の対応関係がずれてしまうのです。

年によって計算方法を変えるのは面倒です。以下のように経過日の基準点を変えて一通りの計算方法で、経過日→月日を計算できるようにします。


3/1 を経過日 0日とし、2月を最後に持って行く

つまり3月を一年の始まり(正確には 3/1 を経過日 0日とする)とし、来年の 2月を当年の最終月 14月として考えます。こうすると経過日と各月の対応関係が固定されるため、2月の長さが変化しても計算に影響が出ません(後ほどまた説明します)

経過日から月日への変換

では実際に経過日から月日を計算してみます。使うのは下記のテーブル(※2)です。

各月 1日の経過日月の長さ
3 0 31日
4 31 30日
5 61 31日
6 92 30日
7 12231日
8 15331日
9 18430日
1021431日
1124530日
1227531日
1330631日
1433728 or 29日

テーブルの 2列目は「その月の 1日を表す経過日」を表しています。調べたい経過日と、2列目で大小比較すれば、調べたい経過日が何月なのかがわかります。何月なのかわかれば何日か?も簡単に分かります。

例として 100 を考えましょう。
まず 92(6/1)< 100 < 122(7/1)から 6月であることがわかります。
さらに 100 - 92(6/1)= 8 ですから 6/1 から 8日後、つまり 6/9 だとわかります。

この計算方法の利点は、各月の 1日に対応する経過日だけ考えれば計算できることです。さきほど 2月の長さは気にしなくて良いと言った理由はここにあります。

2月は最終月のため、長さが変化しても各月の 1日に対応する経過日は変化しません。というより各月の 1日に対応する経過日を変化させないようにするために、わざわざ 2月を最後に回したのです。

実装例

実装する関数の仕様は下記の通りです。

date
3/1 からの経過日を渡します。値域は 0〜365 です。
month
変換後の月を返します。値域は 3〜14(翌年 2月)です。
days
変換後の日を返します。値域は 1〜31 です。
返り値
成功ならば 0 を返します。エラーが起きた場合は -1 を返します。

この処理を C 言語で書くと下記のようになります。

経過日から月日への変換、実装例

int date_to_month(int date, int *month, int *days)
{
        static int m_date[] = {
                0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337,
        };
        int m, d;
        int i;

        if (date < 0 || 365 < date) {
                return -1;
        }

        for (i = 0; i < 12; i++) {
                if (date < m_date[i]) {
                        break;
                }
        }
        m = i + 2;
        d = date - m_date[i - 1] + 1;

        if (month) {
                *month = m;
        }
        if (days) {
                *days = d;
        }

        return 0;
}

負の値や、1年(最長 366日)以上の日数に対しては正常に動作しません。エラー処理として 366以上の値や負の値を渡したときにエラーを返すこととします。

きちんと変換できるか、異常値に対してエラーを返すかどうか、-3 から 369 までの経過日を与えてテストします。

経過日から月日への変換、テスト関数

int main()
{
        int m, d;
        int result, i;

        for (i = -3; i < 370; i++) {
                result = date_to_month(i, &m, &d);
                if (result == -1) {
                        printf("date:%3d -> error\n", i);
                        continue;
                }
                printf("date:%3d -> %2d/%2d\n", i, m, d);
        }

        return 0;
}

実行結果は下記の通りです。適当に端折ってあります。

経過日から月日への変換、実行結果
date: -3 -> error
date: -2 -> error
date: -1 -> error
date:  0 ->  3/ 1
date:  1 ->  3/ 2
...(略)...
date:100 ->  6/ 9
date:101 ->  6/10
date:102 ->  6/11
...(略)...
date:364 -> 14/28
date:365 -> 14/29
date:366 -> error
date:367 -> error
date:368 -> error
date:369 -> error

この関数は月のパラメータに 13月やら 14月を返します。しかし後ほど翌年の 1月、2月へ変換してつじつまを合わせますので、ここでは何も変換しません。

ある月の 1日を表す経過日を求めるには、下記の漸化式を用います。
(ある月の 1日を表す経過日) = (前月の 1日を表す経過日) + (前月の長さ)
要はテーブルの 2列目(各月 1日の経過日)と 3列目(月の長さ)を足すと、次の月の 1日を表す経過日が計算できるということです。

また今度

経過日から月日へ変換できたところで、また今度。次は経過日から年への変換を書く予定です。

[編集者: すずき]
[更新: 2009年 11月 23日 02:32]
link 編集する

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
   2009年 11月 16日 -
      2009年 11月 16日  
link もっと後

管理用メニュー

link 記事を新規作成

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDF ファイル RSS 1.0
QR コード QR コード

最終更新: 8/23 23:38

カレンダー

<2009>
<<<11>>>
1234567
891011121314
15161718192021
22232425262728
2930-----

最近のコメント 5件

  • link 19年07月18日
    hdk 「あっ、AAMはマニュアルのオペレーション...」
    (更新:07/25 00:02)
  • link 19年07月18日
    すずき 「AAM(ASCII Adjust AX ...」
    (更新:07/24 22:22)
  • link 19年07月18日
    hdk 「加算減算は符号のありなしどちらも命令が同...」
    (更新:07/24 07:25)
  • link 19年07月18日
    すずき 「OFをセットして例外を出したければINT...」
    (更新:07/20 11:02)
  • link 19年07月18日
    すずき 「MUL については、結果が倍のビット幅に...」
    (更新:07/20 10:56)

最近の記事 3件

link もっとみる
  • link 19年08月21日
    すずき 「[RockPro64 と linux-next とヘッドフォン] ...」
    (更新:08/23 23:38)
  • link 19年08月12日
    すずき 「[独自の apt サーバー - その 3 - apt の信頼シ] ...」
    (更新:08/12 12:13)
  • link 19年08月11日
    すずき 「[独自の apt サーバー - その 2 - apt-ftpa] ...」
    (更新:08/12 12:13)

こんてんつ

open/close wiki
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報