Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                header
  h1 Generate date&time random

#app
  .results
    .section-header.section-header__results
      h2 Results
      
      //- コピー
      button.button.is-rounded.is-small.copy-results.is-text(:data-clipboard-text="copyAllResults", title="copy")
        span.icon.is-small
          i.fas.fa-copy
      
      //- 再生成
      button.button.is-rounded.is-small.copy-results.is-text(@click.prevent="generateTimes", :data-clipboard-text="copyAllResults", title="generate")
        span.icon.is-small
          i.fas.fa-sync-alt

      .section-header-menu
        .select.is-rounded.is-small
          select(v-model="options.sort")
            option(value="random") random
            option(value="newest") newest
            option(value="oldest") oldest
      
        .select.is-rounded.is-small
          select(v-model="options.num")
            option(value="10") 10
            option(value="25") 25
            option(value="50") 50
            option(value="100") 100
            option(value="250") 250
            option(value="500") 500
          
    table.table.is-fullwidth
      thead
        tr
          th #
          th time
      tbody
        tr(v-for="time, index in results")
          td.index {{ index + 1 }}
          td {{ time }}
      
      
  .options
    .section-header
      h2 Options
    
    //- 時間のフォーマット
    div.field
      label.label Format
      
      .options-item
        input.input(type="text", placeholder="YYYY/MM/DD HH:mm:ss", v-model="options.format")
      
        ul.help
          li: a.button.is-text.is-small(@click.prevent="changeFormat('YYYY/MM/DD HH:mm')") 2016/12/31 01:23
          li: a.button.is-text.is-small(@click.prevent="changeFormat('YYYY/MM/DD')") 2016/12/31
          li: a.button.is-text.is-small(@click.prevent="changeFormat('HH:mm')") 16:23
          li: a.button.is-text.is-small(@click.prevent="changeFormat('YYYY/MM/DD (ddd) HH:mm:ss')") 2016/12/31 (Feb) 01:23:45
          li: a.button.is-text.is-small(@click.prevent="changeFormat('dddd, MMMM Do YYYY, h:mm:ss a')") Feburary, December 31th 2016, 1:23:45 am
          li: a.button.is-text.is-small(@click.prevent="changeFormat('H:mm:ss.SSS')") 16:23:45.012
          
          li: a.button.is-text.is-small(@click.prevent="changeFormat('X')") Unix Timestamp
          li: a.button.is-text.is-small(@click.prevent="changeFormat('x')") Unix Millisecond Timestamp
          
          li: a.button.is-text.is-small(@click.prevent="changeFormat('Qo')") Quarter
          li: a.button.is-text.is-small(@click.prevent="changeFormat('DDDo')") Day of Year
          li: a.button.is-text.is-small(@click.prevent="changeFormat('Wo')") Week of Year
          
    //- 範囲
    div.field
      label.label Range
      
      .options-item
        .columns.is-mobile.is-variable.is-1
          .column
            .input-date
              label.label.is-small start
              input.input.is-small(placeholder="YYYY/MM/DD", v-model="options.range.start", :class="{ 'is-danger': range.start.isValid === false }")
          .column
            .input-date
              label.label.is-small end
              input.input.is-small(placeholder="YYYY/MM/DD", v-model="options.range.end", :class="{ 'is-danger': range.end.isValid === false }")
             
        //- プリセット
        ul.help
          li: a.button.is-text.is-small(@click.prevent="changeRange('today')") today
          li: a.button.is-text.is-small(@click.prevent="changeRange('this week')") this week
          li: a.button.is-text.is-small(@click.prevent="changeRange('this month')") this month
          li: a.button.is-text.is-small(@click.prevent="changeRange('this quarter')") this quarter
          li: a.button.is-text.is-small(@click.prevent="changeRange('this year')") this year
      
    //- 結果から除外する日時
    div.field
      label.label Not (date or time)
      
      .options-item
        .columns.is-mobile.is-variable.is-1(v-for="(item, index) in options.not")
          .column
            .input-date
              label.label.is-small start
              input.input.is-small(placeholder="YYYY/MM/DD or HH:mm", v-model=`item.start`, :class="{ 'is-danger': not[index].isInput && not[index].isValid === false }")
          .column
            .input-date
              label.label.is-small end
              input.input.is-small(placeholder="YYYY/MM/DD or HH:mm", v-model=`item.end`, :class="{ 'is-danger': not[index].isInput && not[index].isValid === false }")
              
script(defer, src="https://use.fontawesome.com/releases/v5.3.1/js/all.js")

              
            
!

CSS

              
                $sidebar-width: 260px;

html {
  background-color: #f5f5f5;
}

body {
  margin: 20px;
}

header {
  padding-top: 2rem;
  padding-bottom: 2rem;
  
  h1 {
    font-size: 1.6rem;
    font-weight: bold;
  }
}

#app {
  width: 100%;
  display: flex;
  align-items: flex-start;
}


// オプション設定
.options {
  display: flex;
  width: $sidebar-width;
  flex-wrap: wrap;
  flex-direction: column;
  border-radius: 6px;
  
  .field {
    &:not(:last-of-type) {
      margin-bottom: 2rem;
    }
  }
  
  .label.is-small {
    color: #757575;
  }
}

.options-item {
  padding: 10px;
  background-color: #e0e0e0;
  border-radius: 2px;
  
  &:not(:last-of-type) {
    margin-bottom: 1rem;
  }
}

// 結果
.results {
  width: calc(100% - #{$sidebar-width});
  box-sizing: border-box;
  margin-right: 20px;
  
  .index {
    width: 2rem;
    color: #aaaaaa;
  }
}


.section-header {
  display: flex;
  padding-top: 0.6rem;
  padding-bottom: 0.6rem;
  
  h2 {
    font-weight: bold;
  }
}


.section-header__results {
  position: sticky;
  top: 0;
  background-color: #f5f5f5;
  
  h2 {
    margin-right: 1rem;
  }
  
  * {
    &:nth-child(4) {
      // 右寄せ
      margin-left: auto;
    }
  }
}
              
            
!

JS

              
                // デフォルトの範囲設定
const end = moment();
const start = end.subtract(1, 'years');

// 時間、日付の正規表現
const timeFormat = new RegExp(/^\s*\d{1,2}\s*:\s*\d{1,2}\s*[\.|:]?\d+$/);
const dateFormat = new RegExp(/^\s*\d{1,2}\s*\/\s*\d{1,2}$/);

// TODO optionsをcookieに保存する?
// TODO 含まない期間を指定できるようにする(複数のstart, end)
// 時間だけのとき、期間のときがある

const state = {
    times: [],  // 生成した年月日

    // 設定(入力 or 選択される)
    options: {
        num: 10,
        sort: 'random',
        format: 'YYYY/MM/DD HH:mm',
        range: {
            // とりあえず今年にしておく
            start: moment().startOf('year').format('YYYY/M/D'),
            end: moment().endOf('year').format('YYYY/M/D')
        },
        // 結果から除外する日時
        not: [
            {start: null, end: null},
            {start: null, end: null},
            {start: null, end: null}
        ]
    }
};


/**
 * 有効な範囲を取得する
 *
 * @param range { start: number, end: number } 日時の範囲
 * @param not { start: number, end: number }[] 含めない範囲
 * @return { start: number, end: number }[] 有効な範囲のリスト
 */
function effectiveRange(range, not) {
    let results = [
        {start: range.start, end: range.end}
    ];

    // notの範囲を削除していく
    // notは昇順になっているのが前提
    not.forEach((notItem) => {
        for (let i = 0, len = results.length; i < len; i++) {
            let result = results[i];

            if (result !== null) {
                if (notItem.start <= result.start && result.end <= notItem.end) {
                    // すべての範囲がnot
                    // 削除
                    results[i] = null;
                } else if (notItem.start <= result.start && result.start <= notItem.end) {
                    // 前半がnot
                    // notで分割する
                    results[i] = {
                        start: notItem.end + 1,
                        end: result.end
                    };
                } else if (notItem.start <= result.end && result.end <= notItem.end) {
                    // 後半がnot
                    results[i] = {
                        start: result.start,
                        end: notItem.start - 1
                    };
                } else if (result.start <= notItem.start && notItem.end <= result.end) {
                    // 途中がnot

                    // 分割する
                    results[i] = {
                        start: result.start,
                        end: notItem.start - 1
                    };

                    const div = {
                        start: notItem.end + 1,
                        end: result.end
                    };

                    results.splice(i + 1, 0, div);
                }
            }
        }
    });

    // console.log('effective range', results);

    // nullの要素を削除する
    return _.compact(results);
}


/**
 * 指定の時間がnotに含まれているかを判別
 *
 * @param time {number} 時間(ミリ秒)
 * @param notTimes {{ start: number, end: number, isCrossOverDay: boolean }} Notの時間(ミリ秒)
 * @param {boolean} Notに含まれているか
 */
function isNotTime(time, notTimes) {
    let result = false;
    const len = notTimes.length;

    for (let i = 0; i < len; i++) {
        const item = notTimes[i];

        if (item.isCrossOverDay === false) {
            if (item.start <= time && time <= item.end) {
                // Notに含まれている
                result = true;
                break;
            }
        } else {
            // 0:00をまたぐ場合
            if ((item.start <= time && time < 86400000) || time <= item.end) {
                // Notに含まれている
                result = true;
                break;
            }
        }
    }

    return result;
}


new Vue({
    el: '#app',
    data: state,

    created: function () {
        this.generateTimes();
    },

    methods: {

        /**
         * 年月日を生成する
         *
         */
        generateTimes: function () {
            const times = [];

            // console.log('start', this.range.start, moment(this.range.start.date, 'x').format('YYYY/M/D HH:mm:ss'), 'end', this.range.end, moment(this.range.end.date, 'x').format('YYYY/M/D HH:mm:ss'));

            // 有効なRange(Range内の日時からNotを取り除いた)をリストにする
            // Notの種類が期間('period')のものを省く

            // 含まない期間を選択
            const notDates = this.not.filter((item) => {
                return item.type === 'period' ? {start: item.start, end: item.end} : false;
            });

            // 含まない時間を抽出して、比較しやすいフォーマットに変換する
            const notTimes = this.not.filter((item) => item.type === 'interval').map((item) => {
                const startTime = item.start.split(':');
                const endTime = item.end.split(':');
                const weight = [60 * 60 * 1000, 60 * 1000, 1000]; // ミリ秒に変換するため時間

                // HH:mm:ddをミリ秒に変換
                const start = startTime.map((item, index) => item * weight[index]).reduce((prev, current) => prev + current);
                const end = endTime.map((item, index) => item * weight[index]).reduce((prev, current) => prev + current);

                return {
                    start,
                    end,
                    isCrossOverDay: end < start // 日をまたぐか(23:00 - 5:00のような場合)
                };
            });

            // {
            //   type: "date"
            //   isInput: true
            //   isValid: true
            //   start: 1514736072
            //   end: 1514769000
            // }

            // Notを省いた期間のリストを生成
            const rangeGroup = effectiveRange({
                start: this.range.start.date,
                end: this.range.end.date
            }, notDates);
            const numRangeGroup = rangeGroup.length; // 期間のグループ数


            // 表示の最大件数分つくっておく
            // 件数やフォーマットを変えたときに再生成させないため
            for (let i = 0; i < 500; i++) {
                const hit = _.random(numRangeGroup - 1);  // どのグループから選ぶか

                let generated = null;

                while (generated === null) {
                    // for (let j = 0; j < 10; j++) {
                    generated = moment(_.random(rangeGroup[hit].start, rangeGroup[hit].end, false), 'x');

                    // Notの時間だったら作り直す
                    const time = Number(generated.format('H')) * 60 * 60 * 1000
                        + Number(generated.format('m')) * 60 * 1000
                        + Number(generated.format('s')) * 1000;

                    const result = isNotTime(time, notTimes);

                    // console.log(time, generated.format('H:m:s'), result);

                    // 含めない時間 or 不正な日付だったら作り直す
                    // Notに日付をまたぐ時間を指定したときに、invalidな日時になることがある?
                    if (result === true || generated.isValid() === false) {
                        generated = null;
                    }
                }

                times.push(generated);
            }

            this.times = times;
        },

        changeFormat: function (format) {
            this.options.format = format;
        },

        /**
         * 範囲プリセットから変更する
         *
         * @param label {string} プリセット名
         */
        changeRange: function (label) {
            switch (label) {
                case 'today': // 今日
                    this.options.range.start = moment().startOf('date').format('YYYY/M/D');
                    this.options.range.end = moment().endOf('date').format('YYYY/M/D');
                    break;

                case 'this week': // 今週
                    this.options.range.start = moment().startOf('week').format('YYYY/M/D');
                    this.options.range.end = moment().endOf('week').format('YYYY/M/D');
                    break;

                case 'this month':  // 今月
                    this.options.range.start = moment().startOf('month').format('YYYY/M/D');
                    this.options.range.end = moment().endOf('month').format('YYYY/M/D');
                    break;

                case 'this quarter':  // 今期
                    this.options.range.start = moment().startOf('quarter').format('YYYY/M/D');
                    this.options.range.end = moment().endOf('quarter').format('YYYY/M/D');
                    break;

                case 'this year':  // 今年
                    this.options.range.start = moment().startOf('year').format('YYYY/M/D');
                    this.options.range.end = moment().endOf('year').format('YYYY/M/D');
                    break;
            }
        }
    },

    computed: {

        /**
         * 表示するための時間を生成
         *
         */
        results: function () {
            const tmp = _.cloneDeep(this.times);	// Vueのレンダリングでvueインスタンス?になるのを防ぐため、複製しておく

            // 件数分を指定の時間フォーマットにする
            const items = tmp.filter((time, index) => index < this.options.num).sort((dateA, dateB) => {
                let result = 0;	// 結果

                const timeA = Number(dateA.valueOf());
                const timeB = Number(dateB.valueOf());

                // 並び替え
                if (this.options.sort === 'newest') {
                    // 新しい順
                    if (timeA < timeB) {
                        result = 1;
                    } else if (timeA > timeB) {
                        result = -1;
                    }
                } else if (this.options.sort === 'oldest') {
                    // 古い順
                    if (timeA > timeB) {
                        result = 1;
                    } else if (timeA < timeB) {
                        result = -1;
                    }
                }

                return result;
            });

            // ソート順を反映する
            return items.map(time => moment(time).format(this.options.format));
        },

        /**
         * 期間の範囲を指定する
         *
         */
        range: function () {
            const start = moment(this.options.range.start);
            const end = moment(this.options.range.end);

            // endは日付指定のみ(時間未指定)の場合、その日いっぱいを含む
            if (end.isValid() && this.options.range.end.match(/^(\d{2,4}\s*\/)?\s*\d{1,2}\s*\/\s*\d{1,2}$/)) {
                end.endOf('day');
            }

            // 入力された時間の範囲をミリ秒に変換する
            // エラーだった場合は、
            return {
                start: {
                    date: start.isValid() ? Number(start.format('x')) : Number(moment().startOf('day').format('x')),
                    isValid: start.isValid()
                },
                end: {
                    date: end.isValid() ? Number(end.format('x')) : Number(moment().endOf('day').format('x')),
                    isValid: end.isValid()
                }
            }
        },

        /**
         * 除外する期間、時間を計算する
         *
         */
        not: function () {
            const result = this.options.not.map((item) => {
                // 入力された日時を補完する
                // 文字列からフォーマットを推測する
                // 開始、終了のどちらかが未入力なら、それ以前、それ以降と判定する
                // 日時がおかしい or フォーマットが不明な場合は無効にする

                let start = item.start; // 除外開始日時
                let end = item.end; // 除外終了日時
                const startDate = moment(item.start);
                let endDate = moment(item.end);

                // 日時 or 時間のみを判定する

                // 書式から判定する
                let type = null;
                let isValid = true;
                let isInput = false;

                // console.log(startDate.format('YYYY/M/D H:mm:ss.SSS'));
                // console.log(endDate.format('YYYY/M/D H:mm:ss.SSS'));

                if (item.start !== null && item.start !== '' && item.end !== null && item.end !== '') {
                    isInput = true;

                    // console.log('---');
                    // console.log(item.start.match(timeFormat), item.end.match(timeFormat));
                    // console.log(startDate.isValid(), endDate.isValid());
                    // console.log(startDate.isValid(), item.end.match(timeFormat));

                    if (item.start.match(timeFormat) && item.end.match(timeFormat)) {
                        // start, endが時間
                        type = 'interval';
                        start = item.start;
                        end = item.end;
                    } else if (startDate.isValid() && endDate.isValid()) {
                        // start, endが日時
                        type = 'period';

                        // 開始と終了をミリ秒で保持
                        start = Number(startDate.format('x'));
                        end = Number(endDate.endOf('day').format('x'));  // その日いっぱいをNotにする

                        if (start >= end) {
                            isValid = false;
                        }
                    } else if (startDate.isValid() && item.end.match(timeFormat)) {
                        // startが日時、endが時間
                        // ex. 2018/1/1, 12:34
                        type = 'period';

                        // 開始と終了をミリ秒で保持
                        start = Number(startDate.format('x'));
                        // end = // 終了時間を計算する
                        endDate = startDate.clone();

                        // startDateとendを組み合わせて、終了時間を生成する
                        // →時間だけ反映する
                        const endTime = end.split(/:|\./i).map((num) => Number(num));
                        endDate.hours(endTime[0] | 0);
                        endDate.minutes(endTime[1] | 0);
                        endDate.seconds(endTime[2] | 0);
                        endDate.milliseconds(endTime[3] | 0);

                        end = Number(endDate.format('x'));

                        if (start >= end) {
                            isValid = false;
                        }
                    } else if (startDate.isValid() && item.end.match(dateFormat)) {
                        // startが日時、endが日付
                        // ex. 2018/1/1, 1/10
                        type = 'period';

                        // 開始と終了をミリ秒で保持
                        start = Number(startDate.format('x'));
                        // end = // 終了時間を計算する
                        end = Number(moment(`${startDate.format('YYYY')}/${item.end}`).endOf('day').format('x')); // その日いっぱいをNotにする

                        if (start >= end) {
                            isValid = false;
                        }
                    } else {
                        isValid = false;
                    }
                }

                // console.log('type', type, 'isValid', isValid, 'start', start, 'end', end);

                return {
                    start,
                    end,
                    type,
                    isValid,
                    isInput
                };
            });

            // console.log('not', result);

            return result;
        },

        /**
         * 結果をコピーできる文字列を取得
         *
         */
        copyAllResults: function () {
            return this.results.join('\n');
        }
    },

    watch: {
        // 範囲が変更されたら生成しなおす
        'range.start.date': function () {
            this.generateTimes();
        },

        // 範囲が変更されたら生成しなおす
        'range.end.date': function () {
            this.generateTimes();
        },

        // 除く日時が更新されたら生成しなおす
        'not': function () {
            this.generateTimes();
        }
    }
});


new ClipboardJS('.copy-results');
              
            
!
999px

Console