Dart 기초 문법

이 문서 주변 탐색

주제 태그, 링크, 시리즈 흐름을 중심으로 옆으로 이동할 수 있습니다.

시리즈 흐름

이 문서는 아직 읽기 시리즈에 연결되지 않았습니다.

관련 문서

같이 읽을 만한 관련 문서가 아직 없습니다.

이 문서를 참조하는 문서

이 문서를 참조하는 다른 문서가 아직 없습니다.

Dart는 Google이 만든 객체지향 언어고, Flutter의 핵심 언어임

온라인 실습 환경: DartPad

1. 변수와 타입

기본 타입

Dart 주요 기본 타입은 이런 것들이 있음

타입

설명

예시

int

정수

int age = 25;

double

실수 (부동소수점)

double height = 175.5;

num

int와 double의 상위 타입

num value = 10;

bool

불리언 (true/false)

bool isActive = true;

String

문자열

String name = '모리';

출력하기

void main() {
  print('Hello, Dart!');

  String name = '모리';
  int age = 25;

  // 문자열 보간 (String Interpolation)
  print('이름: $name');                    // 단순 변수
  print('나이: ${age}살');                  // 중괄호 사용
  print('내년 나이: ${age + 1}살');         // 표현식 사용

  // ⚠️ 주의: 프로퍼티 접근 시 반드시 중괄호 필요
  print('타입: ${name.runtimeType}');       // ✅ 올바름
  // print('타입: $name.runtimeType');      // ❌ 잘못됨 - "모리.runtimeType" 출력
}

변수 스코프 규칙

void main() {
  String name = '모리';
  // String name = '철수';  // ❌ 에러! 같은 스코프에서 중복 선언 불가

  if (true) {
    String name = '철수';   // ✅ 다른 스코프라서 가능
    print(name);            // 철수
  }

  print(name);              // 모리
}

2. var vs 명시적 타입 vs dynamic

🔍 핵심 차이점

특성

var

명시적 타입 (String, int 등)

dynamic

타입 추론

초기값으로 추론

직접 지정

없음 (모든 타입 허용)

타입 변경

❌ 불가

❌ 불가

✅ 가능

컴파일 타임 체크

✅ 있음

✅ 있음

❌ 런타임에만

사용 권장

지역 변수

API, 클래스 필드

피하는 게 좋음

var - 타입 추론

void main() {
  var name = '모리';           // String으로 추론됨
  var age = 25;               // int로 추론됨
  var height = 175.5;         // double로 추론됨

  print(name.runtimeType);    // String
  print(age.runtimeType);     // int

  // ⚠️ 한번 추론된 타입은 변경 불가
  name = '철수';              // ✅ String → String (가능)
  // name = 123;              // ❌ 에러! String → int (불가)

  // 초기값 없이 선언하면 dynamic이 됨
  var something;              // dynamic으로 추론
  something = '문자열';
  something = 123;            // ✅ 가능 (dynamic이니까)
}

명시적 타입 선언

void main() {
  String name = '모리';
  int age = 25;
  double height = 175.5;
  bool isStudent = true;

  // 명시적 선언의 장점
  // 1. 코드 가독성 향상
  // 2. 의도 명확히 표현
  // 3. IDE 자동완성 지원 향상
}

dynamic - 모든 타입 허용

void main() {
  dynamic value = '문자열';
  print(value.runtimeType);   // String

  value = 123;                // ✅ 타입 변경 가능
  print(value.runtimeType);   // int

  value = true;               // ✅ 또 변경 가능
  print(value.runtimeType);   // bool

  // ⚠️ dynamic의 위험성
  dynamic something = '모리';
  // something.someMethod();  // 컴파일은 되지만 런타임 에러!
}

💡 언제 뭘 써야 할까?

// ✅ var 사용 - 지역 변수, 타입이 명확할 때
void processData() {
  var items = <String>[];     // List<String>으로 추론
  var count = 0;              // int로 추론
}

// ✅ 명시적 타입 - 클래스 필드, 함수 파라미터, 반환 타입
class User {
  String name;                // 명시적 선언
  int age;                    // 명시적 선언

  User(this.name, this.age);
}

// ⚠️ dynamic 사용 - JSON 파싱 등 불가피한 경우에만
Map<String, dynamic> json = {
  'name': '모리',
  'age': 25,
  'scores': [90, 85, 95],
};

3. Null Safety

Dart 2.12부터 도입된 Null Safety는 null 관련 에러를 컴파일 타임에 방지해줌

Nullable vs Non-nullable

void main() {
  // Non-nullable (기본값) - null 불가
  String name = '모리';
  // name = null;            // ❌ 에러!

  // Nullable - null 허용 (? 사용)
  String? nickname = '모리';
  nickname = null;           // ✅ 가능

  // int, double 등 모든 타입에 적용됨
  int? age;                  // null로 초기화됨
  print(age);                // null
}

Null 처리 연산자

void main() {
  String? name;

  // 1. Null 체크 후 사용
  if (name != null) {
    print(name.length);      // 안전하게 사용
  }

  // 2. Null assertion (!) - null이 아님을 확신할 때
  String? value = '확실히 있음';
  print(value!.length);      // ⚠️ null이면 런타임 에러!

  // 3. Null-aware 연산자 (?.)
  String? text;
  print(text?.length);       // null이면 null 반환, 에러 없음

  // 4. Null 병합 연산자 (??)
  String? nickname;
  String displayName = nickname ?? '익명';  // null이면 '익명' 사용
  print(displayName);        // 익명

  // 5. Null 병합 할당 (??=)
  int? count;
  count ??= 0;               // null이면 0으로 할당
  print(count);              // 0

  count ??= 10;              // 이미 0이라서 변경 안됨
  print(count);              // 0
}

late 키워드

class User {
  // late - 나중에 초기화할 거라는 약속
  late String name;

  void setName(String value) {
    name = value;
  }

  void printName() {
    print(name);             // 초기화 전 접근하면 런타임 에러
  }
}

// lazy initialization (지연 초기화)
late String heavyData = loadHeavyData();  // 처음 접근할 때만 실행됨

String loadHeavyData() {
  print('데이터 로딩 중...');
  return '무거운 데이터';
}

4. final과 const

기본 개념

void main() {
  // final - 런타임에 한 번만 할당
  final String name = '모리';
  final age = 25;            // 타입 추론 가능
  // name = '철수';          // ❌ 재할당 불가

  // const - 컴파일 타임 상수
  const double pi = 3.14159;
  const gravity = 9.8;       // 타입 추론 가능
  // pi = 3.14;              // ❌ 재할당 불가
}

🔍 final vs const 핵심 차이

특성

final

const

값 결정 시점

런타임

컴파일 타임

DateTime.now()

✅ 가능

❌ 불가

생성자에서 초기화

✅ 가능

❌ 불가

인스턴스 필드

✅ 가능

❌ 불가 (static만)

void main() {
  // final - 런타임 값 가능
  final now = DateTime.now();     // ✅ 실행 시점의 시간
  final random = getRandomNumber(); // ✅ 함수 호출 결과

  // const - 컴파일 타임에 알 수 있는 값만
  const pi = 3.14159;             // ✅ 리터럴
  const doubled = pi * 2;         // ✅ 컴파일 타임 계산 가능
  // const time = DateTime.now(); // ❌ 에러! 런타임에만 알 수 있음
}

int getRandomNumber() => 42;

// 클래스에서의 사용
class Circle {
  final double radius;            // ✅ 인스턴스마다 다를 수 있음
  static const double pi = 3.14159; // ✅ const는 static만 가능

  Circle(this.radius);

  double get area => pi * radius * radius;
}

const 생성자와 컴파일 타임 최적화

class Point {
  final int x;
  final int y;

  // const 생성자
  const Point(this.x, this.y);
}

void main() {
  // const 객체는 동일한 값이면 같은 인스턴스 공유함
  const p1 = Point(1, 2);
  const p2 = Point(1, 2);
  print(identical(p1, p2));  // true - 같은 메모리!

  // 일반 생성자는 매번 새 인스턴스
  var p3 = Point(1, 2);
  var p4 = Point(1, 2);
  print(identical(p3, p4));  // false - 다른 메모리
}

5. 연산자

산술 연산자

void main() {
  int a = 10;
  int b = 3;

  print(a + b);   // 13 (덧셈)
  print(a - b);   // 7  (뺄셈)
  print(a * b);   // 30 (곱셈)
  print(a / b);   // 3.333... (나눗셈, double 반환)
  print(a ~/ b);  // 3  (정수 나눗셈)
  print(a % b);   // 1  (나머지)

  // 증감 연산자
  int count = 0;
  count++;        // 후위 증가
  ++count;        // 전위 증가
  count--;        // 후위 감소
  --count;        // 전위 감소

  // 복합 할당 연산자
  int num = 10;
  num += 5;       // num = num + 5
  num -= 3;       // num = num - 3
  num *= 2;       // num = num * 2
  num ~/= 4;      // num = num ~/ 4
}

비교 연산자

void main() {
  print(5 == 5);   // true  (같음)
  print(5 != 3);   // true  (다름)
  print(5 > 3);    // true  (크다)
  print(5 < 3);    // false (작다)
  print(5 >= 5);   // true  (크거나 같다)
  print(5 <= 3);   // false (작거나 같다)
}

타입 검사 연산자

void main() {
  var value = 42;

  // is - 타입 확인
  print(value is int);      // true
  print(value is String);   // false

  // is! - 타입이 아닌지 확인
  print(value is! String);  // true
  print(value is! int);     // false

  // as - 타입 캐스팅
  num number = 42;
  int integer = number as int;  // ⚠️ 실패하면 런타임 에러
}

논리 연산자

void main() {
  bool a = true;
  bool b = false;

  print(a && b);   // false (AND)
  print(a || b);   // true  (OR)
  print(!a);       // false (NOT)
}

Null-aware 연산자

void main() {
  String? name;

  // ?? - null이면 오른쪽 값 사용
  print(name ?? '기본값');        // 기본값

  // ??= - null이면 할당
  name ??= '모리';
  print(name);                   // 모리

  // ?. - null이면 null 반환, 아니면 접근
  String? text;
  print(text?.length);           // null

  // ?[] - null-aware 인덱스 접근
  List<int>? numbers;
  print(numbers?[0]);            // null
}

삼항 연산자

void main() {
  int age = 20;
  String status = age >= 18 ? '성인' : '미성년자';
  print(status);  // 성인
}

캐스케이드 연산자 (..)

class Person {
  String name = '';
  int age = 0;

  void introduce() {
    print('안녕하세요, $name입니다. $age살이에요.');
  }
}

void main() {
  // 캐스케이드 없이
  var p1 = Person();
  p1.name = '모리';
  p1.age = 25;
  p1.introduce();

  // 캐스케이드 사용 - 메서드 체이닝
  var p2 = Person()
    ..name = '철수'
    ..age = 30
    ..introduce();

  // null-aware 캐스케이드 (?..)
  Person? p3;
  p3?..name = '영희'
    ..age = 28;  // p3가 null이면 아무것도 실행 안됨
}

6. 컬렉션

List (배열)

void main() {
  // 리스트 생성
  List<String> names = ['모리', '철수', '영희'];
  var numbers = <int>[1, 2, 3, 4, 5];  // 타입 추론

  // 빈 리스트 생성
  List<int> emptyList = [];
  var anotherEmpty = <String>[];

  // 기본 연산
  print(names[0]);           // 모리 (인덱스 접근)
  print(names.length);       // 3
  print(names.first);        // 모리
  print(names.last);         // 영희
  print(names.isEmpty);      // false

  // 요소 추가
  names.add('민수');         // 끝에 추가
  names.addAll(['지영', '수진']);  // 여러 개 추가
  names.insert(0, '첫번째'); // 특정 위치에 삽입

  // 요소 제거
  names.remove('철수');      // 값으로 제거
  names.removeAt(0);        // 인덱스로 제거
  names.removeLast();       // 마지막 요소 제거

  // 검색
  print(names.indexOf('모리'));     // 인덱스 반환 (없으면 -1)
  print(names.contains('영희'));    // true/false

  // 유용한 메서드
  var nums = [3, 1, 4, 1, 5, 9, 2, 6];
  nums.sort();                      // 정렬 (원본 변경)
  print(nums.reversed.toList());    // 역순

  // 함수형 메서드
  var doubled = nums.map((n) => n * 2).toList();
  var evens = nums.where((n) => n % 2 == 0).toList();
  var sum = nums.reduce((a, b) => a + b);
}

Spread 연산자 (...)

void main() {
  var list1 = [1, 2, 3];
  var list2 = [4, 5, 6];

  // 스프레드 연산자로 합치기
  var combined = [...list1, ...list2];
  print(combined);  // [1, 2, 3, 4, 5, 6]

  // null-aware 스프레드 (...?)
  List<int>? nullableList;
  var safe = [0, ...?nullableList, 10];
  print(safe);  // [0, 10]

  // 컬렉션 if
  bool showExtra = true;
  var items = [
    'item1',
    'item2',
    if (showExtra) 'extraItem',
  ];

  // 컬렉션 for
  var squares = [
    for (var i = 1; i <= 5; i++) i * i,
  ];
  print(squares);  // [1, 4, 9, 16, 25]
}

Set (집합)

void main() {
  // Set 생성 - 중복 불허, 순서 없음
  Set<String> fruits = {'사과', '바나나', '오렌지'};
  var numbers = <int>{1, 2, 3, 4, 5};

  // 빈 Set (주의: {}는 Map이 됨!)
  var emptySet = <String>{};
  Set<int> anotherEmpty = {};

  // 요소 추가/제거
  fruits.add('포도');
  fruits.add('사과');        // 중복은 무시됨
  fruits.remove('바나나');

  // 집합 연산
  var a = {1, 2, 3, 4};
  var b = {3, 4, 5, 6};

  print(a.union(b));         // {1, 2, 3, 4, 5, 6} (합집합)
  print(a.intersection(b));  // {3, 4} (교집합)
  print(a.difference(b));    // {1, 2} (차집합)

  // List → Set (중복 제거)
  var listWithDupes = [1, 2, 2, 3, 3, 3];
  var uniqueSet = listWithDupes.toSet();
  print(uniqueSet);  // {1, 2, 3}
}

Map (딕셔너리)

void main() {
  // Map 생성
  Map<String, String> capitals = {
    'Korea': 'Seoul',
    'Japan': 'Tokyo',
    'China': 'Beijing',
  };

  // 타입 추론
  var scores = {
    'math': 90,
    'english': 85,
    'science': 95,
  };

  // 빈 Map
  var emptyMap = <String, int>{};

  // 값 접근 및 수정
  print(capitals['Korea']);         // Seoul
  capitals['USA'] = 'Washington';   // 추가
  capitals['Korea'] = 'Seoul City'; // 수정

  // 여러 개 추가
  capitals.addAll({
    'France': 'Paris',
    'Germany': 'Berlin',
  });

  // 안전한 접근
  print(capitals['Unknown']);       // null
  print(capitals['Unknown'] ?? '없음');  // 없음

  // 유용한 메서드
  print(capitals.keys);             // 모든 키
  print(capitals.values);           // 모든 값
  print(capitals.entries);          // 키-값 쌍
  print(capitals.containsKey('Korea'));   // true
  print(capitals.containsValue('Seoul')); // false (수정됨)

  // 순회
  capitals.forEach((key, value) {
    print('$key의 수도는 $value');
  });

  // Map 변환
  var upperCaseCapitals = capitals.map(
    (key, value) => MapEntry(key, value.toUpperCase()),
  );
}

7. 조건문과 반복문

if-else 문

void main() {
  int score = 85;

  // 기본 if-else
  if (score >= 90) {
    print('A등급');
  } else if (score >= 80) {
    print('B등급');
  } else if (score >= 70) {
    print('C등급');
  } else {
    print('재시험');
  }

  // 삼항 연산자 (간단한 조건)
  String result = score >= 60 ? '합격' : '불합격';
}

switch 문

void main() {
  String grade = 'A';

  switch (grade) {
    case 'A':
      print('우수');
      break;
    case 'B':
      print('양호');
      break;
    case 'C':
    case 'D':  // fall-through
      print('보통');
      break;
    default:
      print('노력 필요');
  }

  // Dart 3.0+ switch expression
  String message = switch (grade) {
    'A' => '우수합니다!',
    'B' => '잘했습니다!',
    'C' || 'D' => '조금 더 노력하세요.',
    _ => '재시험이 필요합니다.',
  };
}

for 반복문

void main() {
  // 기본 for 문
  for (int i = 0; i < 5; i++) {
    print('반복 $i');
  }

  // for-in (컬렉션 순회)
  List<String> fruits = ['사과', '바나나', '오렌지'];
  for (String fruit in fruits) {
    print(fruit);
  }

  // for-in with index가 필요할 때
  for (int i = 0; i < fruits.length; i++) {
    print('$i: ${fruits[i]}');
  }

  // forEach (함수형)
  fruits.forEach((fruit) {
    print(fruit);
  });

  // forEach with arrow
  fruits.forEach((fruit) => print(fruit));
}

while 반복문

void main() {
  // while
  int count = 0;
  while (count < 3) {
    print('while: $count');
    count++;
  }

  // do-while (최소 1번 실행)
  int num = 0;
  do {
    print('do-while: $num');
    num++;
  } while (num < 3);
}

반복 제어

void main() {
  // break - 반복문 탈출
  for (int i = 0; i < 10; i++) {
    if (i == 5) break;
    print(i);  // 0, 1, 2, 3, 4
  }

  // continue - 다음 반복으로
  for (int i = 0; i < 5; i++) {
    if (i == 2) continue;
    print(i);  // 0, 1, 3, 4 (2 건너뜀)
  }

  // 라벨 사용 (중첩 반복문)
  outer: for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      if (i == 1 && j == 1) break outer;
      print('$i, $j');
    }
  }
}

8. Enum

기본 Enum

enum Status {
  pending,
  approved,
  rejected,
}

void main() {
  Status status = Status.pending;

  print(status);              // Status.pending
  print(status.name);         // pending
  print(status.index);        // 0

  // 모든 값 접근
  print(Status.values);       // [Status.pending, Status.approved, Status.rejected]

  // switch와 함께 사용
  switch (status) {
    case Status.pending:
      print('대기 중');
      break;
    case Status.approved:
      print('승인됨');
      break;
    case Status.rejected:
      print('거절됨');
      break;
  }
}

Enhanced Enum (Dart 2.17+)

enum HttpStatus {
  ok(200, 'OK'),
  notFound(404, 'Not Found'),
  internalError(500, 'Internal Server Error');

  final int code;
  final String message;

  const HttpStatus(this.code, this.message);

  // 메서드 추가도 가능함
  bool get isSuccess => code >= 200 && code < 300;

  @override
  String toString() => '$code: $message';
}

void main() {
  var status = HttpStatus.ok;
  print(status.code);        // 200
  print(status.message);     // OK
  print(status.isSuccess);   // true
  print(status);             // 200: OK

  // 값으로 찾기
  var found = HttpStatus.values.firstWhere(
    (s) => s.code == 404,
    orElse: () => HttpStatus.internalError,
  );
  print(found);  // 404: Not Found
}

9. 함수

기본 함수

// 반환 타입 명시
int add(int a, int b) {
  return a + b;
}

// void - 반환값 없음
void greet(String name) {
  print('안녕하세요, $name님!');
}

// 화살표 함수 (한 줄 표현식)
int multiply(int a, int b) => a * b;
void sayHello() => print('Hello!');

void main() {
  print(add(3, 5));       // 8
  greet('모리');          // 안녕하세요, 모리님!
  print(multiply(4, 5));  // 20
}

선택적 매개변수

// 1. 위치 기반 선택적 매개변수 (대괄호)
String introduce(String name, [int? age, String? city]) {
  var result = '이름: $name';
  if (age != null) result += ', 나이: $age';
  if (city != null) result += ', 도시: $city';
  return result;
}

// 기본값 설정
int addNumbers(int a, [int b = 0, int c = 0]) {
  return a + b + c;
}

void main() {
  print(introduce('모리'));                    // 이름: 모리
  print(introduce('모리', 25));               // 이름: 모리, 나이: 25
  print(introduce('모리', 25, '서울'));        // 이름: 모리, 나이: 25, 도시: 서울

  print(addNumbers(10));           // 10
  print(addNumbers(10, 20));       // 30
  print(addNumbers(10, 20, 30));   // 60
}

명명된 매개변수 (Named Parameters)

// 중괄호 사용, required 또는 기본값 필요
void createUser({
  required String name,
  required int age,
  String country = 'Korea',
  String? nickname,
}) {
  print('이름: $name, 나이: $age, 국가: $country');
  if (nickname != null) print('별명: $nickname');
}

void main() {
  // 순서 상관없이 이름으로 전달
  createUser(
    age: 25,
    name: '모리',
  );

  createUser(
    name: '철수',
    age: 30,
    country: 'Japan',
    nickname: '철이',
  );
}

혼합 사용

// 위치 필수 + 명명된 선택적
void example(int required1, String required2, {int? optional1, String optional2 = 'default'}) {
  print('$required1, $required2, $optional1, $optional2');
}

void main() {
  example(1, 'two');
  example(1, 'two', optional1: 3);
  example(1, 'two', optional2: 'custom', optional1: 3);
}

일급 객체로서의 함수

void main() {
  // 함수를 변수에 할당
  int Function(int, int) operation = add;
  print(operation(3, 5));  // 8

  operation = multiply;
  print(operation(3, 5));  // 15

  // 함수를 인자로 전달
  print(calculate(10, 20, add));       // 30
  print(calculate(10, 20, multiply));  // 200

  // 익명 함수
  var numbers = [1, 2, 3, 4, 5];
  var doubled = numbers.map((n) => n * 2).toList();
  print(doubled);  // [2, 4, 6, 8, 10]
}

int add(int a, int b) => a + b;
int multiply(int a, int b) => a * b;

int calculate(int a, int b, int Function(int, int) operation) {
  return operation(a, b);
}

클로저 (Closure)

void main() {
  var counter = makeCounter();
  print(counter());  // 1
  print(counter());  // 2
  print(counter());  // 3

  var anotherCounter = makeCounter();
  print(anotherCounter());  // 1 (새로운 클로저)
}

Function makeCounter() {
  int count = 0;  // 클로저가 캡처하는 변수
  return () {
    count++;
    return count;
  };
}

10. typedef와 함수 타입

typedef 정의

// 함수 타입 별칭 정의
typedef Operation = int Function(int x, int y, int z);
typedef StringCallback = void Function(String message);
typedef Predicate<T> = bool Function(T value);

// 함수 구현
int add(int x, int y, int z) => x + y + z;
int multiply(int x, int y, int z) => x * y * z;

void main() {
  // typedef를 타입으로 사용
  Operation op = add;
  print(op(1, 2, 3));  // 6

  op = multiply;
  print(op(1, 2, 3));  // 6

  // 고차 함수에서 활용
  print(calculate(10, 20, 30, add));       // 60
  print(calculate(10, 20, 30, multiply));  // 6000
}

// typedef를 매개변수 타입으로 사용
int calculate(int x, int y, int z, Operation operation) {
  return operation(x, y, z);
}

제네릭 typedef

typedef Mapper<T, R> = R Function(T input);
typedef Reducer<T> = T Function(T a, T b);

void main() {
  // String → int 매퍼
  Mapper<String, int> stringLength = (s) => s.length;
  print(stringLength('Hello'));  // 5

  // int → String 매퍼
  Mapper<int, String> intToString = (n) => 'Number: $n';
  print(intToString(42));  // Number: 42

  // Reducer
  Reducer<int> sum = (a, b) => a + b;
  var numbers = [1, 2, 3, 4, 5];
  print(numbers.reduce(sum));  // 15
}

실전 활용 예시

// 콜백 패턴
typedef OnSuccess<T> = void Function(T data);
typedef OnError = void Function(String error);

void fetchData({
  required OnSuccess<String> onSuccess,
  required OnError onError,
}) {
  try {
    // 데이터 가져오기 시뮬레이션
    var data = '가져온 데이터';
    onSuccess(data);
  } catch (e) {
    onError(e.toString());
  }
}

void main() {
  fetchData(
    onSuccess: (data) => print('성공: $data'),
    onError: (error) => print('에러: $error'),
  );
}

11. DateTime과 Duration

DateTime - 날짜와 시간

void main() {
  // 현재 시간
  var now = DateTime.now();
  print(now);  // 2026-02-01 14:30:45.123456

  // 특정 날짜 생성
  var birthday = DateTime(1995, 12, 25);  // 1995년 12월 25일
  print(birthday);  // 1995-12-25 00:00:00.000

  // 시간까지 지정
  var meeting = DateTime(2026, 3, 15, 14, 30, 0);  // 2026년 3월 15일 14:30:00
  print(meeting);

  // UTC 시간
  var utcNow = DateTime.now().toUtc();
  var utcDate = DateTime.utc(2026, 1, 1, 12, 0, 0);
}

DateTime 속성

void main() {
  var now = DateTime.now();

  print(now.year);        // 2026
  print(now.month);       // 2 (1-12)
  print(now.day);         // 1 (1-31)
  print(now.hour);        // 14 (0-23)
  print(now.minute);      // 30 (0-59)
  print(now.second);      // 45 (0-59)
  print(now.millisecond); // 123 (0-999)
  print(now.microsecond); // 456 (0-999)

  print(now.weekday);     // 6 (1=월요일, 7=일요일)
  print(now.isUtc);       // false
}

DateTime 비교

void main() {
  var date1 = DateTime(2026, 1, 1);
  var date2 = DateTime(2026, 6, 15);
  var date3 = DateTime(2026, 1, 1);

  // 비교 연산자
  print(date1.isBefore(date2));  // true
  print(date1.isAfter(date2));   // false
  print(date1.isAtSameMomentAs(date3));  // true

  // compareTo
  print(date1.compareTo(date2)); // -1 (date1 < date2)
  print(date2.compareTo(date1)); // 1  (date2 > date1)
  print(date1.compareTo(date3)); // 0  (같음)
}

DateTime 연산

void main() {
  var now = DateTime.now();

  // 더하기 (Duration 사용)
  var tomorrow = now.add(Duration(days: 1));
  var nextWeek = now.add(Duration(days: 7));
  var inTwoHours = now.add(Duration(hours: 2));

  // 빼기
  var yesterday = now.subtract(Duration(days: 1));
  var lastMonth = now.subtract(Duration(days: 30));

  // 두 날짜 사이의 차이 (Duration 반환)
  var date1 = DateTime(2026, 1, 1);
  var date2 = DateTime(2026, 3, 15);
  var difference = date2.difference(date1);

  print(difference.inDays);   // 73
  print(difference.inHours);  // 1752
}

Duration - 시간 간격

void main() {
  // Duration 생성 방법들
  var oneDay = Duration(days: 1);
  var twoHours = Duration(hours: 2);
  var thirtyMinutes = Duration(minutes: 30);
  var fiveSeconds = Duration(seconds: 5);
  var hundredMs = Duration(milliseconds: 100);

  // 여러 단위 조합
  var complex = Duration(
    days: 1,
    hours: 2,
    minutes: 30,
    seconds: 45,
  );
  print(complex);  // 26:30:45.000000

  // 총 시간 얻기
  print(complex.inDays);         // 1
  print(complex.inHours);        // 26
  print(complex.inMinutes);      // 1590
  print(complex.inSeconds);      // 95445
  print(complex.inMilliseconds); // 95445000
}

Duration 연산

void main() {
  var d1 = Duration(hours: 2);
  var d2 = Duration(minutes: 30);

  // 더하기, 빼기
  print(d1 + d2);  // 2:30:00.000000
  print(d1 - d2);  // 1:30:00.000000

  // 곱하기, 나누기
  print(d1 * 2);   // 4:00:00.000000
  print(d1 ~/ 2);  // 1:00:00.000000 (정수 나눗셈)

  // 비교
  print(d1 > d2);  // true
  print(d1 < d2);  // false
  print(d1 == Duration(minutes: 120));  // true

  // 절대값
  var negative = Duration(hours: -5);
  print(negative.abs());  // 5:00:00.000000

  // 음수 체크
  print(negative.isNegative);  // true
}

비동기에서 Duration 사용

Future<void> main() async {
  print('시작: ${DateTime.now()}');

  // 2초 대기
  await Future.delayed(Duration(seconds: 2));

  print('2초 후: ${DateTime.now()}');

  // 500ms 대기
  await Future.delayed(Duration(milliseconds: 500));

  print('0.5초 후: ${DateTime.now()}');
}

날짜 포맷팅

void main() {
  var now = DateTime.now();

  // 기본 toString
  print(now.toString());  // 2026-02-01 14:30:45.123456

  // ISO 8601 형식
  print(now.toIso8601String());  // 2026-02-01T14:30:45.123456

  // 직접 포맷팅
  String formatted = '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';
  print(formatted);  // 2026-02-01

  // 시간 포맷팅
  String timeFormatted = '${now.hour}:${now.minute.toString().padLeft(2, '0')}';
  print(timeFormatted);  // 14:30
}

// 💡 복잡한 포맷팅은 intl 패키지의 DateFormat 쓰면 됨
// import 'package:intl/intl.dart';
// DateFormat('yyyy-MM-dd HH:mm').format(now);

문자열 → DateTime 파싱

void main() {
  // ISO 8601 형식 파싱
  var parsed = DateTime.parse('2026-02-01 14:30:00');
  print(parsed);  // 2026-02-01 14:30:00.000

  // 다양한 형식 지원
  var date1 = DateTime.parse('2026-02-01');
  var date2 = DateTime.parse('2026-02-01T14:30:00');
  var date3 = DateTime.parse('2026-02-01T14:30:00Z');  // UTC

  // tryParse - 실패하면 null 반환 (에러 없음)
  var invalid = DateTime.tryParse('invalid-date');
  print(invalid);  // null

  var valid = DateTime.tryParse('2026-03-15');
  print(valid);  // 2026-03-15 00:00:00.000
}

실전 예시

void main() {
  // 나이 계산
  var birthday = DateTime(1995, 12, 25);
  var today = DateTime.now();
  var age = today.year - birthday.year;
  if (today.month < birthday.month ||
      (today.month == birthday.month && today.day < birthday.day)) {
    age--;  // 아직 생일 안 지남
  }
  print('나이: $age세');

  // D-day 계산
  var targetDate = DateTime(2026, 12, 31);
  var daysLeft = targetDate.difference(today).inDays;
  print('D-$daysLeft');

  // 특정 요일 구하기
  var weekdays = ['', '월', '화', '수', '목', '금', '토', '일'];
  print('오늘은 ${weekdays[today.weekday]}요일');

  // 월의 마지막 날
  var lastDayOfMonth = DateTime(today.year, today.month + 1, 0);
  print('이번 달 마지막 날: ${lastDayOfMonth.day}일');
}

🤖

본 글은 AI의 도움을 받아 작성되었습니다.

읽기 시리즈

현재 위치를 확인하고, 흐름을 따라 바로 다음 읽기로 이어갈 수 있습니다.

Reading Series

Dart
0편

현재 읽는 문서

Dart 기초 문법

Dart 언어에 대해서 알아보기

이전 문서

시리즈의 시작 문서입니다.

다음 문서

시리즈의 마지막 문서입니다.

Dart 기초 문법 | 위키.모리.블로그_