iOSで実装されているカレンダーの解説

この記事を読んでいる人は、日本語で書いているので概ね西暦か和暦を使っていると思います。しかし、世界には西暦では過ごしていない国や地域が数多くある。もしグローバル展開する機会があればそれぞれの国で正しく計算されたり表示される助けになればと思います。

カレンダーの隣の日付はグレゴリアン暦2020年3月14日をそれぞれのカレンダーで表示したもの。

太陽暦

太陽暦とは

太陽の変化によって一年を決める方法。太陽を地球が一周するのに365.25日程度かかるため、1年を365日と4年に1回の閏年で調整を行う暦。月と季節は同期する。

グレゴリオ暦

グレゴリオ暦と同じ日付を採用する暦をグレゴリオ暦系とした。 太陽年は365.25日ではなく誤差があり、400年間に(100回ではなく)97回の閏年置いて調整を行っている。

グレゴリオ暦

iso8601 Saturday, March 14, 2020 gregorian Saturday, March 14, 2020 iso8601 にも定義されているように国際標準の暦として採用されている。世界的にも多く採用されている。 キリスト紀元として、キリストが誕生した年を1年とした。多くの暦は紀元1年の前年は紀元前1年であり、0年は存在しない。

タイ仏暦

buddhist Saturday, March 14, 2563 BE 釈迦が入滅した翌年を元年とする紀年法である。キリスト紀元に543を加えた値が仏滅紀元となる。

中華民国暦

republicOfChina Saturday, March 14, 109 Minguo

中華民国が成立した1912年を元年とする紀年法である。

和暦

japanese ここで紹介する全ての暦の中で最も変わった紀元をもつ。改元を行う暦である。多くの暦は紀元を基準にするか紀元前と紀元後しか存在しない。一方、和暦は237個の元号が存在し、30年程度で改元が行われる。改元される理由は近年では天皇陛下が御即位されることによって改元され、直近にならないといつ改元されるかは予測不可能である。平成31年4月30日の翌日は、令和1年5月1日である。つまり、時代(era)が一つ増え、年が1に戻り、月以下は通常通りである。平成31年と令和1年はeraとyearが異なっているが同じ年として扱われる。

海外では改元されることを想定していないOSSが多く存在するため注意が必要である。

コプト暦系

1年は13ヶ月あり、初めの12ヶ月は各30日から成り、13月に追加日5日(閏年は6日)を足した365日(366日)である。 太陽年との誤差を修正していない365.25日を1年の長さとしている。通常は1年を365日として4年ごとに閏年をせってしている。ユリウス暦と同期しており、グレゴリアン暦に比べて400年で3日進むため、徐々にズレが生じる。

コプト

coptic Saturday, Baramhat 5, 1736 ERA1 ディオクレティアヌス紀元を紀元とている。ディオクレティアヌス紀元は、ディオクレティアヌスが即位した年の年初(ユリウス暦284年8月29日)を紀元とする

エチオピア

ethiopicAmeteMihret Saturday, Megabit 5, 2012 ERA1

キリストの生誕を基準にしている。

ethiopicAmeteAlem Saturday, Megabit 5, 7512 ERA0

神はキリストの生誕の5500年前に世界を創造したとして、世界創造暦を基準としている。

太陰太陽暦

1月を29.5日、12ヶ月を1年として、季節の連れを修正するために、3年に1回程度

インド国定暦

indian Saturday, Phalguna 24, 1941 Saka

サカ紀元の紀元に当たる西暦78年を「インド国定暦0年」として数える。

イラン暦

persian Saturday, Esfand 24, 1398 AP

中国暦

chinese Saturday, Second Month 21, 2020(geng-zi)

ユダヤ暦

hebrew Saturday, 18 Adar 5780 at 12:55:29 AM Japan Standard Time

太陰暦

1ヶ月を29.5日、12ヶ月で1年としたもの。

イスラム暦

islamic Saturday, Rajab 20, 1441 AH islamicCivil Saturday, Rajab 19, 1441 AH islamicTabular Saturday, Rajab 20, 1441 AH islamicUmmAlQura Saturday, Rajab 19, 1441 AH

イスラム暦の少しづつ違うようだが細かくは調べきれていない。分かる人コメントください。

参照

The Ethiopic Calendar | An Ethiopian Journal

iOSでユーザーが文字を入力する方法

UITextFieldなどに文字を入力する方法が、キーボードだけではないのでまとめておこうと思います。

ソフトウェアキーボード

もっとも一般的な入力方法。 日本語の変換があるので確定していない文字が存在する。確定した時点で、複数の文字が入れ替わったりすることもある。

外部キーボード

Bluetoothなどでつながったキーボードを使って、文字を入力することができる。ほとんどは、ローマ字入力だが、バーコードリーダーなどの特殊な入力をするようなキーボードも存在する

カットやペースト

 大量の文字を一度に削除したり、入力することになる。必ず一文字ずつ入力していると考えているとバグの原因になる。 数字をペーストをする際は前後にスペースが入ったりする。

元に戻すとやり直す

シェイクしたりアラートを表示したり、iPadではキーボードにボタンをタップするなど、元に戻すを行うことができます。もちろんやり直すこともできる。 ユーザーの入力を変更していたりすると、クラッシュする場合がある。 https://qiita.com/akuraru/items/15e629bfe472f83f45f6

他のテキストからドラッグする

選択している状態で、ドラッグすると文字がそのまま別のテキストに貼り付けることができます。結果としてペーストと同じような挙動になります。

f:id:akuraru:20200326223617p:plain

他にも入力方法を知っていたらコメントください。

あなたの文字制限をしているUITextFieldはクラッシュしているかもしれない

UITextFieldで入力した文字に対して何らかの制限を加えたい時はよくあると思います。文字数を制限したり、数字のみを入力させたい場合などです。

サンプルコードを用意しました。 https://github.com/akuraru/CrashTextField

数字のみを入力制限させたい場合の雑なサンプルは以下のようなものになると思います。このコードはクラッシュします。

extension ViewController: UITextFieldDelegate {
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        let b = string.isEmpty || string == String(Int(string) ?? 0)
        return b
    }
}

クラッシュの再現方法

  1. UITextFieldに制限される文字列をペーストする
  2. 端末をシェイクして取り消しアラートを表示する(シミュレータだと⌘+⌃+Z)
  3. 取り消す
  4. クラッシュ

対策1

入力を受け付けない場合、UndoManagaerをリセットします。これで、取り消しができなくなります。

extension ViewController: UITextFieldDelegate {
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        let b = string.isEmpty || string == String(Int(string) ?? 0)
        
        if !b {
            textField.undoManager?.removeAllActions()
        }
        
        return b
    }
}

対策2

シェイクで編集する機能を無効にします。この方法はアプリ全体で無効になってしまうのでできればやらないようにしたほうがいいでしょう。

application.applicationSupportsShakeToEdit = false

クラッシュの内容(おまけ)

アプリケーションのどこで起こったクラッシュなのか全くわからないクラッシュログが発生する

2018-05-21 22:20:05.268665+0900 CrashTextField[47789:859156] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSBigMutableString substringWithRange:]: Range {0, 38} out of bounds; string length 0'
*** First throw call stack:
(
    0   CoreFoundation                      0x00000001127fd1e6 __exceptionPreprocess + 294
    1   libobjc.A.dylib                     0x000000010ec76031 objc_exception_throw + 48
    2   CoreFoundation                      0x0000000112872975 +[NSException raise:format:] + 197
    3   Foundation                          0x000000010e663e72 -[NSString substringWithRange:] + 131
    4   UIKit                               0x0000000110360b1e -[NSTextStorage(UIKitUndoExtensions) _undoRedoAttributedSubstringFromRange:] + 136
    5   UIKit                               0x0000000110360f7d -[_UITextUndoOperationReplace undoRedo] + 319
    6   Foundation                          0x000000010e711695 -[_NSUndoStack popAndInvoke] + 280
    7   Foundation                          0x000000010e711424 -[NSUndoManager undoNestedGroup] + 433
    8   UIKit                               0x000000010f5225aa __58-[UIApplication _showEditAlertViewWithUndoManager:window:]_block_invoke.2495 + 31
    9   UIKit                               0x000000010f8db425 -[UIAlertController _invokeHandlersForAction:] + 105
    10  UIKit                               0x000000010f8dbe2a __103-[UIAlertController _dismissAnimated:triggeringAction:triggeredByPopoverDimmingView:dismissCompletion:]_block_invoke.461 + 16
    11  UIKit                               0x000000010f683d02 -[UIPresentationController transitionDidFinish:] + 1346
    12  UIKit                               0x000000010f687b72 __56-[UIPresentationController runTransitionForCurrentState]_block_invoke.436 + 183
    13  UIKit                               0x000000011026b274 -[_UIViewControllerTransitionContext completeTransition:] + 102
    14  UIKit                               0x000000010f5d410d -[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:] + 859
    15  UIKit                               0x000000010f5a6f09 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 343
    16  UIKit                               0x000000010f5a754c -[UIViewAnimationState animationDidStop:finished:] + 293
    17  UIKit                               0x000000010f5a7600 -[UIViewAnimationState animationDidStop:finished:] + 473
    18  QuartzCore                          0x00000001160117a9 _ZN2CA5Layer23run_animation_callbacksEPv + 323
    19  libdispatch.dylib                   0x000000011395d848 _dispatch_client_callout + 8
    20  libdispatch.dylib                   0x000000011396892b _dispatch_main_queue_callback_4CF + 628
    21  CoreFoundation                      0x00000001127bfc99 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
    22  CoreFoundation                      0x0000000112783ea6 __CFRunLoopRun + 2342
    23  CoreFoundation                      0x000000011278330b CFRunLoopRunSpecific + 635
    24  GraphicsServices                    0x0000000115189a73 GSEventRunModal + 62
    25  UIKit                               0x000000010f5120b7 UIApplicationMain + 159
    26  CrashTextField                      0x000000010e36db77 main + 55
    27  libdyld.dylib                       0x00000001139da955 start + 1
)

参考

https://www.jianshu.com/p/b302a7c66741

XCUITestでアプリの初期状態を変更する

起動時にパラメータを渡すことができ、アプリケーション内でいつでもそれを確認することができます。 XCUITestでは外部からの操作がほとんどできません。この起動時のパラメータによって初期状態を変更することができそうです。

let app = XCUIApplication()
app.launchArguments.append("UITEST")
app.launch()
ProcessInfo.processInfo.arguments.contains("UITEST")

リモートワークは銭湯ですべき

前職での話です

リモートワーク制度で諸事情によって会社にこれないための制度です。 諸事情の解釈は通常家にいないといけない理由がある的なものかもしれませんが、会社の環境(主にネットワークあたりとか狭いとか)よくないということを理由により良い環境で働くためにリモートワークすることにしました。 より良い環境が必ずしも自宅とは限らない。 適当にググって良さそうだったのでなごみの湯にリモートワークしにいくことにしました。

https://www.google.co.jp/amp/s/multiness.net/tripandwork/nomad-super-sento/amp/

なごみの湯とは

https://www.nagomino-yu.com/ 都内の日帰り温泉のこと。 新宿から10分程度の荻窪にあり、駅からすぐ近くにあり割と簡単に行ける。

良い点

  • 会社のWifiより若干早い
  • 広い。すごく広い。誰も働いていないので3席分くらい占有できる。
  • 15分で温泉に浸かって休憩できる
    • タバコ休憩並なので、共用されていいはず
  • コンビニにおやつ買ってくる短くて健康的
  • 詰まったら温泉入りに行ける
  • サウナとかもある
  • リクライニングソファとかがある
  • 隣が秋吉

悪い点

  • 2000円くらいかかる
  • そんなにはお高くはない
  • 電源があるところの椅子は硬い
  • 一度入ったら出れない
  • 食べてから行くか、中で食べるか迷う
  • もちろんディスプレイとかはない
  • 個人的な理由
    • 会社より遠い
    • おそらく中央線ユーザ以外は大抵の人が会社より遠くなりがち

勤怠的な話

勤怠は現状のシステムとしては、出社して退社した後に再び出社して退社した場合はその間が休憩時間としてあるかわれます。よってTeamSpiritで論理出社と退社ボタンを使って休憩時間を記録することができます。食事などをしている時間をし始めるタイミングで退社を行い、終わったら出社することで正確な勤怠管理が可能です。

その他

  • 漫画がある
    • 読んでいないものがあれば漫画喫茶より安く済むかも
  • 縦に長い施設
  • 女子の方が休憩施設に近い
  • 女子風呂が2階で休憩施設が地下1階、男子は4階
  • 働いている人は2,3人見かけた

転職と原宿のおすすめの店10選

1月15日に退職して、16日にクレディセゾンに入社しました。給料と環境は向上した。エンジニア募集しているそうです。

ということでおすすめの原宿の10選です。

がらり

焼酎と味噌のお店。お昼は3種類の味噌汁が無限に飲める。何食べても旨い。そして常に混んでいる。

夜は味噌舐めながら、焼酎を煽る末期な飲み方がしていた。

https://www.google.co.jp/amp/s/s.tabelog.com/tokyo/A1309/A130901/13005444/top_amp/

タップルーム

クラフトビールのお店。昼はやっていない(はず)。常に10種類以上のクラフトビールがある。

今のところ飲み屋しかないが、1杯で潰れるので飲み屋は出てこない。

https://bairdbeer.com/ja/taprooms/

龍の子

麻婆豆腐のお店。会社からは竹下通りを通り抜けていくがそれだけの価値はある。麻婆豆腐以外もあるが、麻婆豆腐しか食べた事はない。

http://ryunoko.tokyo

Hutte

グリルバル。ステーキが食べれる店。ステーキを食べようと思ったらかなり長距離を移動しないとないので、会社から2番目に近い飲食店というのもあり得点が高い。

https://www.google.co.jp/amp/s/amp.retty.me/r/100001311117/

スパゴ

パスタ専門店。パスタの種類がかなりある。食べた限りでは全部美味しかった。たらこがおすすめ。

https://www.google.co.jp/amp/s/amp.retty.me/r/100000019992/

asatte

日替わり定食のみのお店。入るとご飯の量しか聞かれないので初見殺し感がある。毎日変わるので毎日行ける店。

https://www.instagram.com/asatte._.menu/?hl=ja

東京バーガー

東京感はない。なんなら常連はバーガーも食べない。ライスシリーズのタコライスロコモコを食べている。

https://g.co/kgs/FqQzPH

うさぎ

広島のお好み焼き屋。

https://g.co/kgs/XL2mqp

手打ち釜揚げうどん 禅

カレーうどんがおすすめ

https://g.co/kgs/FfvWEc

五○二

定食とうどんのお店

https://g.co/kgs/geR5f1

まとめ

原宿でも勧めれる10個もあったことに驚いている。一番のおすすめは同僚と食べにいく店です。

なぜSwiftでDIが流行っているのか

結論としてはテストを簡単にするためです。 DIでなくても簡単にする方法はあります。 この記事では、DIすることによってテストが簡単になる理由を説明します。

偽物によるテストの容易性の向上

 あるアプリを手動で正しく動いていることをチェックするとしよう。多くのアプリは外部の状態に依存しています。サーバーや時間、ユーザーの操作などです。アプリがちゃんと動いていることをチェックするために、それぞれがちゃんと動いていることを確かめたり、状態を確認しながらチェックをする必要があります。チェックを行うために、外側の環境の不安定で一向にアプリのチェックが進まない事は良くある話です。

 実際の開発を行うときは、手動でのチェックは極力避けていると思います。テストコードを書いてCIで自動テストを実行しています。1日のうちで何度も自動テストを行うのに外側の不安定な環境に依存していてはテストが失敗し、自動テストの効果が十分に発揮できません。

 本物の代わりに偽物を使って、テストを行えば安定したテストを行うことができます。例えば、必ず決まった値を返すサーバーのようなものや、同じ時間を返す時計、ユーザーのような操作をするロボットを使うことです。

テストダブルによるオブジェクトの置き換え

 話を小さくして、オブジェクト単位の話です。

 オブジェクトもアプリと同じように別のオブジェクトに依存しています。別のオブジェクトもさらに別のオブジェクトに依存しています。自動テストを行うときに、本物のオブジェクトを使ってテストをすると数十のオブジェクトを状態をセットアップしなければいけない時があります。テストのための準備するコードが実際にテストを行なっているコードより圧倒的に長くなるのは本末転倒です。テストしたい対象に焦点を当てたコードの方が良いテストです。

 偽物のオブジェクトを使うことによって、本物より早くセットアップできれば、テストしたい対象に焦点を当てたテストコードを書くことができます。この偽物のオブジェクトをテストダブルと言います。

Swiftによる型チェック

 偽物のオブジェクトに置き換えればテストが簡単になるという話をしてきましたが、Swiftでは少し面倒な問題があります。

 Swiftは強い型付き言語であり、最適化をしなければ実行時の型チェックも行われる言語です。変数の型が構造体の場合はその構造体のオブジェクトの必要になり、クラスに依存している場合はそのクラスかそのクラスを継承しているクラスのオブジェクトの必要がありです。無理矢理に型を誤魔化しても変数に代入しても実行時エラーになります。つまり、普通にコードを書くとテストダブルは使えず、常に本物のオブジェクトを使うしかありません。これは、テストを行うときに巨大なセットアップが必要なオブジェクトが存在し、テストを困難にさせます。

プロトコルによるテストの容易性の向上と実体の喪失

 変数の型をプロトコルにすることで、プロトコルを実装しているオブジェクトなら何でも代入することができます。テストダブルが使えるため、意図した動作をテストが用意になります。その代わりに、変数の依存をプロトコルにすることによって、本物の型がわからなくなってしまいます。テストしやすくするためには仕方ないことだったとは言え、このままではアプリを為すことができません。

オブジェクトの注入(DI)

 アプリとしての形を為すためにプロトコルでの依存している本物のオブジェクトを解決する必要があります。本物のオブジェクトを解決するためのパターンがDIなのです。DIによって、アプリとしての形を保ちつつ、テストしやすいコードを書くことができるのです。

 DIの実装方法はいくつかあり、DICやCakeパターンなどがあります。実装方法については自分で調べてください。