6/13 Date객체와 메서드 / 일출, 일몰시간 표시하기 / 랜덤옵션 추가

낄낄박사·2024년 6월 13일
post-thumbnail

일출, 일몰시간 표시하면서 정리해본 Date 객체와 메서드

  • 날짜와 시간 얻기 -> Date 객체 사용: new Date()을 사용하면 현재 날짜와 시간을 나타내는 Date 객체가 생성됨.

  • Date 객체를 다양한 메서드를 통해 다른 형식으로 출력할 수 있음. Date안에는 다양한 메서드가 있음.

    • ISO 문자열 (toISOString 메서드)
    • UTC 문자열 (toUTCString 메서드)
    • 로컬 날짜 및 시간 문자열 (toLocaleString 메서드) 등
  • Date 객체의 개별 구성 요소를 추출할때 쓸 수 있는 메서드들 예시

    		console.log(currentDate.getFullYear()); // 예: 2024
    		console.log(currentDate.getMonth() + 1); 
            // 예: 6 (월은 0부터 시작하므로 1을 더함)
    		console.log(currentDate.getDate()); // 예: 12
    		console.log(currentDate.getHours()); // 예: 14
    		console.log(currentDate.getMinutes()); // 예: 20
    		console.log(currentDate.getSeconds()); // 예: 30
    
  • 현재 시간을 밀리초 단위의 정수로 받기: Date 객체와 getTime() 메서드를 사용

    const now = new Date();
    const timestamp = now.getTime();
    console.log(timestamp); // 예: 1686568830000 (밀리초 단위의 정수 형태)
  • 현재 시간을 초 단위의 정수로 받기:밀리초 단위 대신 초 단위로 받고 싶으면, 밀리초 단위를 1000으로 나누면 됨

    const now = new Date();
    const timestampInSeconds = Math.floor(now.getTime() / 1000);
    console.log(timestampInSeconds); // 예: 1686568830 (초 단위의 정수 형태)
  • Date.now() 메서드: 현재 시간을 밀리초 단위의 정수 형태로 반환!

    • Date.now()는 new Date().getTime()과 동일한 결과를 반환하지만, 객체를 생성하지 않고 바로 현재 타임스탬프를 얻을 수 있어서 더 간단함
  • padStart: JavaScript의 String 객체 메서드로, 문자열의 시작 부분을 특정 문자로 채워 지정된 길이의 문자열을 반환해줌.

    • 두 개의 인수를 받음. 최종 문자열의 길이, 문자열을 채울 문자
    • 주로 숫자를 두 자리 형식으로 맞추기 위해 사용할 수 있음. 예를 들어, 숫자 5를 "05"로 포맷팅할 때
    const number = 5;
    const formattedNumber = number.toString().padStart(2, '0');
    console.log(formattedNumber); // "05"
    
    • padStart 메서드는 문자열의 길이를 특정 길이로 맞추기 위해 필요한 경우 앞에 지정한 문자를 추가함. 문자열 "11"을 padStart(2, '0')로 포맷팅하면 길이가 이미 2이므로 앞에 0이 추가되지 않음
  • Date 객체 생성자에는 다양한 형태의 인수를 사용할 수 있다.

    • 밀리초 /초 단위 스탬프, ISO 8610문자열, UTC 문자열, 날짜와 시간 문자열, 개별 숫자 인수 ==> 해당 인수들을 받아 객체로 생성

OpenWeatherMap 데이터를 이용한 일출, 일몰시간 나타내기

메세지로 나오는 게 좋기는 한데, 그래도 구체적인 날씨정보를 수치로 확인하고 싶은 마음이 들었음.
그래서 메세지 area를 클릭하면 detail이 보이도록 만들어 보았음.

openWeatherMap에서 날씨 데이터에는 아래와 같은 내용이 포함되어 있음

  sys: {
    type: 1,
    id: 8105,
    country: 'KR',
    sunrise: 1718223019,
    sunset: 1718276047
  },
  timezone: 32400,

이 중에서 내가 사용할 부분은 sunrise, sunset,timezone

sunrise와 sunset은 시간을 초단위 타임스탬프로 보여주고 있음. 그래서,

  1. 이 녀석들을 우선 밀리세컨즈로 변환하여 Date 객체를 생성하고

  2. 그리고 getUTCHours()와 getUTCMinutes()를 사용해서 시간, 분을 추출하고

  3. padStart를 사용하여 두자리로 포맷팅 할 것이다!

    코드

export function convertSuntime(timestamp: number) {
  // 밀리세컨즈로 변환하여 Date 객체 생성
  const date = new Date(timestamp * 1000);
  // 시간, 분 추출 + 두 자리 포맷팅
  const hours = date.getHours().toString().padStart(2, "0");
  const minutes = date.getMinutes().toString().padStart(2, "0");

  return `${hours}:${minutes}`;
}

사용부

 <p>
   일출: <span>{convertSuntime(sunrise)}</span> | 일몰:
   <span>{convertSuntime(sunset)}</span>
 </p>

결과

Ta-da!

  • 시간, 분을 추출할 때 getUTCHours, getUTCMinutes를 사용하면 특정 타임존에 맞는 시간을 보여줄 수 있다. 특정 지역의 일출/일몰 시간을 계산해서 보여주고 싶을때 쓸 수 있겠음.

  • 그냥 getHours, getMinutes를 쓰면 시스템의 로컬 시간대로 변환하기 때문에 시스템의 로컬 시간대에 따라 결과가 달라질 수 있음!

  • 나는 특정 지역의 일출/일몰 시간을 사용자의 로컬 시간대로 보고 싶기 때문에 그냥 getHours, getMinutes를 사용함

    캐스터 랜덤 옵션 추가

  1. 기존 Caster 타입을 이용해서 ExtendedCaster 타입 생성
  2. 선택 옵션 CASTERTS 배열에 "랜덤"추가
  3. handleOptions에 클릭된 옵션이 "랜덤"일 경우 Math.random으로 랜덤하게 인덱스를 받아서 setCaster 해주기
  4. casterContext에도 새로만든 extendedCaster로 타입 변경
    5.CasterAvatar 컴포넌트에 수정: 기존애 아바타 이미지 클릭시 배열 순서대로 Caster를 변경하던 기능삭제
  • 처음에 CasterAvatar 컴포넌트에서 랜덤으로 인덱스를 받아서 캐스터 정보를 가져오게끔 했는데, 그랬더니 아바타 이미지가 안나오는 문제가 있었음.
  • CasterAvatar 컴포넌트가 아니라 CasterOptions 컴포넌트에서 setCaster할때 랜덤하게 캐스터를 넘겨주도록 변경해야했음!

완성 코드

//src>app>components>CasterOptions.tsx

import { Caster } from "../service/openai";
import { useCaster } from "../context/CasterContext";

type Props = {
  onClose: () => void;
};
export type ExtendedCaster = Caster | "랜덤";

const CASTERS: ExtendedCaster[] = [
  "랜덤",
  "할머니",
  "이장님",
  "엄마",
  "여자캐스터",
  "남자캐스터",
  "KPOP매니아",
  "먹방유튜버",
];
export default function CasterOptions({ onClose }: Props) {
  const { setCaster } = useCaster();

  const handleOptions = (event: React.MouseEvent<HTMLLIElement>) => {
    const clickedText = event.currentTarget.innerText as ExtendedCaster;

    if (clickedText === "랜덤") {
      const randomIndex = Math.floor(Math.random() * CASTERS.length);
      setCaster(CASTERS[randomIndex] as Caster);
    } else {
      setCaster(clickedText);
    }
    onClose();
  };

  return (
    <ul className="flex flex-col items-center w-full h-full justify-around ">
      {CASTERS.map((caster) => (
        <li
          onClick={handleOptions}
          className="hover:cursor-pointer flex justify-center items-center w-full h-full  hover:bg-indigo-50 border-b"
          key={caster}
        >
          {caster}
        </li>
      ))}
    </ul>
  );
}
// src>app>components>CasterAvatar.tsx

import Image from "next/image";
import { useCaster } from "../context/CasterContext";
import { Caster } from "../service/openai";
import { useState } from "react";
import ModalPortal from "./ModalPortal";
import CasterOptions from "./CasterOptions";
import CasterModal from "./CasterModal";

type CasterDetail = {
  name: Caster;
  src: string;
};

export const CASTERS: CasterDetail[] = [
  { name: "할머니", src: "/images/grandma.webp" },
  { name: "이장님", src: "/images/viliageChief.webp" },
  { name: "엄마", src: "/images/mom.webp" },
  { name: "여자캐스터", src: "/images/femaleCaster.webp" },
  { name: "남자캐스터", src: "/images/maleCaster.webp" },
  { name: "KPOP매니아", src: "/images/kpopMania.webp" },
  { name: "먹방유튜버", src: "/images/foodVlogger.webp" },
];

export default function CasterAvatar() {
  const [openModal, setOpenModal] = useState(false);
  const { caster } = useCaster();

  const currentCaster = CASTERS.find((c) => c.name === caster);

  return (
    <section className="flex flex-col py-2">
      <div className="relative w-[400px] h-[400px]">
        {currentCaster && (
          <Image
            priority
            src={currentCaster.src}
            alt={`image of ${currentCaster.name} `}
            fill
          />
        )}
      </div>
      <button
        className="mt-5 p-3 bg-white bg-opacity-50 rounded-md font-bold hover:bg-opacity-30"
        onClick={() => setOpenModal(true)}
      >
        {caster}
      </button>
      {openModal && (
        <ModalPortal>
          <CasterModal onClose={() => setOpenModal(false)}>
            <CasterOptions onClose={() => setOpenModal(false)} />
          </CasterModal>
        </ModalPortal>
      )}
    </section>
  );
}
// src>app>context>CasterAvatar.tsx

import React, {
  Dispatch,
  SetStateAction,
  createContext,
  useContext,
  useState,
} from "react";

import { ExtendedCaster } from "../components/CasterOptions";

type CasterContextType = {
  caster: ExtendedCaster;
  setCaster: Dispatch<SetStateAction<ExtendedCaster>>;
};

export const CasterContext = createContext<CasterContextType>({
  caster: "할머니",
  setCaster: () => {},
});

export const CasterProvider = ({ children }: { children: React.ReactNode }) => {
  const [caster, setCaster] = useState<ExtendedCaster>("할머니");
  return (
    <CasterContext.Provider value={{ caster, setCaster }}>
      {children}
    </CasterContext.Provider>
  );
};

export const useCaster = () => useContext(CasterContext);

0개의 댓글