datecount

日数カウンターを拡張する

さて、Dateの加減算は日単位だけではありません。年月単位でも可能なので、少し凝った仕様にしてみましょう。

オリジナルのdate counterのオプションスイッチ”-a”ないし”-b”はそのままに、値の指定方法を拡張します。

  • 指定期間の単位は年/月/週/日の単位で組み合わせ指定可とする
  • 各々の単位記号はy/m/w/dとする
  • 1は省略指定可能(5日⇒5d、1日⇒1dまたはd)
  • 日単位のみ指定時は単位記号dを省略指定可能(5日⇒5dまたは5)

具体的には「今日から7年と5か月と3週間と1日後」を問い合わせる場合、

$ date
2016年 2月 1日 月曜日 15時29分13秒 JST
$ datecount -a 7y5m3w1d
7years 5months 22days after is Sun Jul 23 2023

となります。

#! /usr/bin/swift
import Foundation
var msg: [String] = ["", "date counter ver.1.3 (c)2015 Takeru-chan\nusage: datecount -[a|b] [n(d)][(n)d][(n)w][(n)m][(n)y]", "datecount: Specified term is not in range A.D.1100..9999."]
var msg_status: Int32 = 0
let month_odr: [String] = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
let week_odr: [String] = ["Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat"]
var diff = (year:0, month:0, day:0, buffer:"")
// NSDate型の引数を受け取り、NSDateComponents型を返す関数
func getCalComp(date: Date) -> DateComponents {
    let cal: NSCalendar = NSCalendar(identifier: NSCalendar.Identifier.gregorian)!
    let components: DateComponents = cal.components([.year, .month, .day, .weekday], from:date)
    return components
}
// fmt文字列解析
func analyzeStr(fmt_str: String) -> (Int, Int, Int, String) {
    diff.buffer = ""
    chk_char: for char in fmt_str.characters {
        switch char {
        case "d":
            if diff.buffer == "" {
                diff.buffer = "1"
            }
            diff.day = diff.day + Int(diff.buffer)!
            diff.buffer = ""
        case "w":
            if diff.buffer == "" {
                diff.buffer = "1"
            }
            diff.day = diff.day + Int(diff.buffer)! * 7
            diff.buffer = ""
        case "m":
            if diff.buffer == "" {
                diff.buffer = "1"
            }
            diff.month = diff.month + Int(diff.buffer)!
            diff.buffer = ""
        case "y":
            if diff.buffer == "" {
                diff.buffer = "1"
            }
            diff.year = diff.year + Int(diff.buffer)!
            diff.buffer = ""
        case "0","1","2","3","4","5","6","7","8","9":
            diff.buffer = diff.buffer + String(char)
        default:
            msg_status = 1
            break chk_char
        }
    }
    if diff.buffer != "" {
        diff.day = diff.day + Int(diff.buffer)!
        diff.buffer = ""
    }
    if msg_status != 1 {
        if diff.year == 1 {
            msg[0] = "\(diff.year)year "
        } else if diff.year != 0 {
            msg[0] = "\(diff.year)years "
        }
        if diff.month == 1 {
            msg[0] = msg[0] + "\(diff.month)month "
        } else if diff.month != 0 {
            msg[0] = msg[0] + "\(diff.month)months "
        }
        if diff.day == 1 {
            msg[0] = msg[0] + "\(diff.day)day "
        } else if diff.day != 0 {
            msg[0] = msg[0] + "\(diff.day)days "
        }
    }
    return diff
}
// 実質プログラムの始まり
let arguments: [String] = CommandLine.arguments
if arguments.count == 3 {
    switch arguments[1] {
    case "-a":
        analyzeStr(fmt_str:arguments[2])
        msg[0] = msg[0] + "after is "
    case "-b":
        analyzeStr(fmt_str:arguments[2])
        diff.year = -1 * diff.year
        diff.month = -1 * diff.month
        diff.day = -1 * diff.day
        msg[0] = msg[0] + "before is "
    default:
        msg_status = 1
    }
    if msg_status != 1 {
        let now: Date = Date()
        var cal_comp: DateComponents = getCalComp(date:now)
        cal_comp.day = cal_comp.day! + diff.day
        cal_comp.month = cal_comp.month! + diff.month
        cal_comp.year = cal_comp.year! + diff.year
        var result_date: Date = Calendar.current.date(from:cal_comp)!
        cal_comp = getCalComp(date:result_date)
        msg[0] = msg[0] + week_odr[cal_comp.weekday! - 1] + " " + month_odr[cal_comp.month! - 1]
        if cal_comp.year! >= 1100 && cal_comp.year! <= 9999 {
            msg[0] = msg[0] + " \(cal_comp.day!) \(cal_comp.year!)"
        } else {
            msg_status = 2
        }
    }
} else {
    msg_status = 1
}
print("\(msg[Int(msg_status)])\n")
exit(msg_status)

以前の日数カウンターでは数字しか受け付けなかったところに文字が追加されました。

これまで引数として与えられた文字列はノーチェックで使用していました。プログラムが許可する文字列と異なる場合には有無を言わせずエラー扱いしていたのですが、今回は仕様の都合上、文字列解析が必要になります。

とはいえとても簡単な解析しかしないので、引数の順序が違っていたりいくつかの引数に分割して指定されていたら正しく動きません。正しいフォーマットで与えられた文字列を指定意図通りに分解するだけです。

この部分を関数として抜き出したのが14〜72行目。与えられた文字列を先頭から順に1文字づつチェックしています。数字であればバッファにコピーし、単位記号が出てきたらバッファにある数字を年月日それぞれの変数に代入します。

バッファが空であれば1を指定されたと解釈し、最後の文字が数字であればそれまでバッファに溜め込まれた数字は日数であると解釈しています。

週ごとの演算は直接はできないので、単純に7倍して日数として処理しています。